[SOLVED] [Math/Geometry/Mesh-Generation] Planetary Surface

Hello everyone!

I need a little bit of help with something.
Problem is solved. Look at the bottom of this post.


The Problem:
I am working on a 3D Planetary-Engine in Java (yes, you read right, ‘3D Planets in Java’), and I am stuck at correctly generating the surface of the planet, or to be more exact, the Mesh of the Terrain-Patches the surface consists of.


What I am doing right now:
Right now, I am using the CubeToSphere system in combination with a Quad-Tree.
That is the part that works like a charm. I cna create new node’s in the QuadTree, and remove them if no longer needed.
Dynamically, at runtime, and it could happen at ANY moment in time as the ‘camera’ (Usually the Player) moves around.

The problem I am having is to give each Node in the QuadTree a section of Terrain.
These Terrain ‘Patches’ have a size of 32x32 3D-Points, which get later transformed onto the surface of the planetary sphere, including a height-offset from a height-map which is also procedurally generated using a simple fractal algorythm (a weird mix of Diamond-Square and Simplex-Noise).

So far so good, but how do I go about actually generating the points in between the 4 given edge-points which make up the borders of the Node?
I basically have to generate a Mesh of Points in between 4 arbitrary points in 3D-Space.

And:
I have no idea how to do this, as I am somehow having problems thinking up any good way to do it, nor do I seem to find any good resources on doing this per Google.
If you happen to know any good resources on this: Link(s) please!

Additional Notes:

  • If needed, I can add a few more variables to the QuadTree-Node’s, if that helps.
  • I don’t to create any objects except Points in the code (for obvious reasons -> Camera + Arbitrary Movement + Dynamic QuadTree = Memory Garbage en’ masse).
  • I would like to NOT use recursion in the generation (if there is no other way, I will use recursion in the code).
  • I CAN and I AM already using Multi-Threading in the application. But concurrency should not be that big of a problem.

[s]That should be enough of a description.

Help please?[/s]

The solution is as follows.

Code Info:

  • I didn’t optimize anything.
  • I didn’t use any external libraries.
  • Result gets stored in ‘vertices’ in the order X,Y,Z,X,Y,Z, …
  • Hardcoded to generate a field of 32x32 points.
  • Its VERY fast just like this already.

The Code to do the thing I wan’t:



		float[] A = new float[]{ -2.0f, +1.75f, 0.0f };
		float[] B = new float[]{ +1.0f, +1.0f, 0.0f };
		float[] C = new float[]{ +1.5f, -1.25f, 0.0f };
		float[] D = new float[]{ -1.0f, -1.5f, 0.0f };
		
		
		float[] vertices = new float[32 * 32 * 3];
		int shift = 32 * 31;
		
		// PASS ONE, Top and Bottom Rows!
		float lerp, div;
		for(int i = 0, io = 0, io2 = 0; i < 32; i++)
		{
			div = i;
			io = i*3;
			io2 = (i+shift)*3;
			lerp = div / 31f;
			
			// A[0], B[0]
			vertices[io++] = A[0] + (lerp * (B[0] - A[0]));
			vertices[io++] = A[1] + (lerp * (B[1] - A[1]));
			vertices[io] = A[2] + (lerp * (B[2] - A[2]));
			
			vertices[io2++] = D[0] + (lerp * (C[0] - D[0]));
			vertices[io2++] = D[1] + (lerp * (C[1] - D[1]));
			vertices[io2] = D[2] + (lerp * (C[2] - D[2]));
		}
		
		// PASS TWO, All dem rest!
		for(int ix = 0, ixo, ixo2; ix < 32; ix++)
		{
			
			// Get the Top and Bottom points.
			ixo = ix * 3;
			ixo2 = (ix+shift) * 3;
			float tx = vertices[ixo++];
			float ty = vertices[ixo++];
			float tz = vertices[ixo];
			
			float bx = vertices[ixo2++];
			float by = vertices[ixo2++];
			float bz = vertices[ixo2];
			
			for(int iy = 1, index; iy <= 31; iy++)
			{
				index = (ix + (iy*32)) * 3;
				div = iy;
				lerp = div / 31f;
				
				vertices[index++] = tx + (lerp * (bx - tx));
				vertices[index++] = ty + (lerp * (by - ty));
				vertices[index] = tz + (lerp * (bz - tz));
			}
		}

Thanks for the help.

  • Longor1996

I don’t quite understand what you’re saying here. Why do you need to generate points in between the 4 edge points in the first place?

To create a 3D-Grid that can then be transformed to represent the surface of the Planet.

So you’re struggling to map the points on your noise graph to a 3D mesh?

I think you didn’t read the Main-Post right:
I am having problems creating the Points which make up the 3D-Mesh.

The mapping of the generated heightmap values to the ‘height’ of the points is not a problem.

I think I am understanding your problem.
So youve got these 4 points in 3D space, and you need to generate points between them, like filling the space, correct?
Do the points need to be convex to represent the curve of the planet?

Here is a simple method to fill with points between two points in space:


public ArrayList createPointsBetween(Vector3f pointA, Vector3f pointB, int numOfPoints) {
ArrayList<Vector3f> returnPoints = new ArrayList<Vector3f>();
float distanceX = pointA.x - pointB.x;
float distanceY = pointA.y - pointB.y;
float distanceZ = pointA.z - pointB.z;

float spaceingX = distanceX/numOfPoints;
//Do for y and z

for (int i = 0; i != numOfPoints; i ++) {
returnPoints.add(new Vector3f(
          pointA.x+(i*spaceingX),
          pointA.y+(i*spaceingY),
          pointA.z+(i*spaceingZ)));
}
return returnPoints;
}

That will probably work. Cant test it, on a mobile device.
Once you have the createPointsBetween() working you can adapt it to solve your problem (which I assume was generating points between 4 arbitrairy points) :


public void createPointsBetween(Vector3f[] points, int numberOfPoints) {

ArrayList<Vector3f> pointsA = createPointsBetween(points[0], points[1], numberOfPoints);
ArrayList<Vector3f> pointsB = createPointsBetween(points[2], points[3], numberOfPoints);

for (int i = 0; i != numberOfPoints; i ++) {
createPointsBetween(pointsA.get(i), pointsB.get(i), numberOfPoints);
}

}

I am probably throwing some syntax errors in there, but the concept is there.

Wow.

That actually helped a lot.

I was stuck at the thought that I have to generate the whole grid of points at once, and just now it hit me in the face what was wrong.

Solution to my Problem:

  1. Take the 4 Points.
  2. Take Point A and B.
  3. Generate 32 points at fixed intervals between A & B, thus creating a ‘top’ row.
  4. Generate 32 points at fixed intervals between C & D, thus creating a ‘bottom’ row.
  5. Then go and generate the other rows using the now given ‘top’ and ‘bottom’ points.
  6. ???
  7. YAY!

Thank you very much!
Have a nice day!

  • Longor1996

You could also use method 2, with splat textures.
This allows you to have some lod (what means you can implement infinite zoom/details theoretically).

  1. Generate cube with x points (devide every side in x squares, for example 32 quads each side) with normal texture coordinates.
  2. Normalise every point.
  3. Generate texture with noise, mapping heigt on 1 channel (for example red).
  4. In the vertex shader, do something like:

    float height = texture(heightmap, textcoord).r;

    vec4 vec = vec4(vert * (1+height), 1.0);
    gl_Position = projection * matrix * vec;

  1. In the fragment shader, do the same with the terrain textures (you got 3 channels left on your splatmap)
  2. ??
  3. Profit

I already get LOD by using a QuadTree and building meshes for the Terrain-Patches.
What you propose makes sense if I where to do some Terrain-effects on the GPU instead of pre-calculating them.

Or did I understand your post wrong and you mean something else?

  • Longor1996

Yeah, this way is also pre calculated (and brings some extra options for gpu animations yes).
I dont know how your planets are looking, but with textures (splat mapping) you can generate much more detail with much less triangles.
I have tested both, and textures are much better in performance as in memory, an additional benefit is that textures are easily stored and edited.

Also how are you sending terrain data to the shader?, i guess some extra bytes with the triangles, whether its dirt, grass or something else.
This will also make your buffers very big and ugly.
Im curious about how you are implementing this.

I am still at the step to generate the actual points that make up the triangles the terrain consists of.
To be frankly, I already planned in using textures to store information about the terrain.

I still need to generate vertices to send to the GPU.

Good thing: I got it working!

Have a picture of ‘4-Point Arbitrary Grid-Mesh’ generation:

http://abload.de/img/arbitraty4pointgridmez9kza.png

And here is the code.

  • I didn’t optimize anything.
  • I didn’t use any external libraries.
  • Result gets stored in ‘vertices’ in the order X,Y,Z,X,Y,Z, …
  • Hardcoded to generate a field of 32x32 points.
  • Its VERY fast just like this already.


		float[] A = new float[]{ -2.0f, +1.75f, 0.0f };
		float[] B = new float[]{ +1.0f, +1.0f, 0.0f };
		float[] C = new float[]{ +1.5f, -1.25f, 0.0f };
		float[] D = new float[]{ -1.0f, -1.5f, 0.0f };
		
		
		float[] vertices = new float[32 * 32 * 3];
		int shift = 32 * 31;
		
		// PASS ONE, Top and Bottom Rows!
		float lerp, div;
		for(int i = 0, io = 0, io2 = 0; i < 32; i++)
		{
			div = i;
			io = i*3;
			io2 = (i+shift)*3;
			lerp = div / 31f;
			
			// A[0], B[0]
			vertices[io++] = A[0] + (lerp * (B[0] - A[0]));
			vertices[io++] = A[1] + (lerp * (B[1] - A[1]));
			vertices[io] = A[2] + (lerp * (B[2] - A[2]));
			
			vertices[io2++] = D[0] + (lerp * (C[0] - D[0]));
			vertices[io2++] = D[1] + (lerp * (C[1] - D[1]));
			vertices[io2] = D[2] + (lerp * (C[2] - D[2]));
		}
		
		// PASS TWO, All dem rest!
		for(int ix = 0, ixo, ixo2; ix < 32; ix++)
		{
			
			// Get the Top and Bottom points.
			ixo = ix * 3;
			ixo2 = (ix+shift) * 3;
			float tx = vertices[ixo++];
			float ty = vertices[ixo++];
			float tz = vertices[ixo];
			
			float bx = vertices[ixo2++];
			float by = vertices[ixo2++];
			float bz = vertices[ixo2];
			
			for(int iy = 1, index; iy <= 31; iy++)
			{
				index = (ix + (iy*32)) * 3;
				div = iy;
				lerp = div / 31f;
				
				vertices[index++] = tx + (lerp * (bx - tx));
				vertices[index++] = ty + (lerp * (by - ty));
				vertices[index] = tz + (lerp * (bz - tz));
			}
		}

Saucymeatman’s posts where enough to finally get me on the right track to solve this really (really) simple problem.
Really. It’s easy.

And I just couldn’t get it done, because I thought I have to make all the points at once in a single loop.
That tiny little stupid thought stopped me from continuing working on my project for weeks now.
8 weeks to be exactly.

That’s what you get from being under constant stress.
You are unable to solve even the simplest of problems.

Now I got it solved. Finally.

  • Longor1996

Oh oke, i thought you were further and i wanted to save you from the same mistake as me :).

Looks clean im using something like this (kind of an abdomnation already):

//Parent quadsphere


public QuadSphere(Planet parent){
        this.parent = parent;

        Orientation[] vals = Orientation.values();
        chunks = new QuadSphereChunk[vals.length];
        for(int i = 0; i < vals.length; i++){
            chunks[i] = new QuadSphereChunk(this, vals[i].getStartPos(parent.getSize()/2), parent.getSize(), vals[i]);
            chunks[i].setChunk();
        }
    }

//The 6 sides of the cube (behaves like an quad tree each).


public void setChunk(){
        if(texture == null && planetque == null){
            planetque = PlanetFactory.Instance.addQue(parent.getPlanet(), this, parent.getTextSize(), orientation, textoffset, size);
        }
    }
    
    public void TextureReady(){
        if(parent != null){
            texture = planetque.getHeightMap();
            splattexture = planetque.getSplatMap();

            vbo = new VertexBuffer();
            Build(vbo, 4, parent.getSize(), parent.getSize() / size);

            vbo.addVertexbufferAttrib(new VertexbufferAttrib("vert", GL_FLOAT, 3, SH_PLANET.getAttribLoc("vert")));
            vbo.addVertexbufferAttrib(new VertexbufferAttrib("text", GL_FLOAT, 2, SH_PLANET.getAttribLoc("text")));
            vbo.Create();

            planetque = null;
        }
    }

private void Build(VertexBuffer vbo, int depth, float w, float factor){
        if(depth > 0){
            Split();
            children[0].Build(vbo, depth-1, w, factor);
            children[1].Build(vbo, depth-1, w, factor);
            children[2].Build(vbo, depth-1, w, factor);
            children[3].Build(vbo, depth-1, w, factor);
        }else{
            TrianglePoint_Planet c1, c2, c3, c4;
            Vector3f p1, p2, p3, p4;
            Vector2f text1 = new Vector2f(), text2 = new Vector2f(), text3 = new Vector2f(), text4 = new Vector2f();
            p1 = (Vector3f)new Vector3f(pos1).normalise().scale(parent.getSize());
            p2 = (Vector3f)new Vector3f(pos2).normalise().scale(parent.getSize());
            p3 = (Vector3f)new Vector3f(pos3).normalise().scale(parent.getSize());
            p4 = (Vector3f)new Vector3f(pos4).normalise().scale(parent.getSize());
            
            float center = 0.5f*factor;
            Vector2f.add((Vector2f)orientation.getMask2D(pos1).scale(1f/w*factor), new Vector2f(center, center), text1);
            Vector2f.add((Vector2f)orientation.getMask2D(pos2).scale(1f/w*factor), new Vector2f(center, center), text2);
            Vector2f.add((Vector2f)orientation.getMask2D(pos3).scale(1f/w*factor), new Vector2f(center, center), text3);
            Vector2f.add((Vector2f)orientation.getMask2D(pos4).scale(1f/w*factor), new Vector2f(center, center), text4);
            
            int minx = (int)SimpleMath.min(text1.x, text2.x, text3.x, text4.x);
            int miny = (int)SimpleMath.min(text1.y, text2.y, text3.y, text4.y);
            Vector2f sub = new Vector2f(minx, miny);
            Vector2f.sub(text1, sub, text1);
            Vector2f.sub(text2, sub, text2);
            Vector2f.sub(text3, sub, text3);
            Vector2f.sub(text4, sub, text4);
            
            c1 = new TrianglePoint_Planet(p1, text1);
            c2 = new TrianglePoint_Planet(p2, text2);
            c3 = new TrianglePoint_Planet(p3, text3);
            c4 = new TrianglePoint_Planet(p4, text4);
            
            triindex = vbo.addTriangle(c1, c2, c3).getID();
            vbo.addTriangle(c3, c4, c1);
        }
    }

    private void Split(){
        if(children == null){
            float newsize = size/2;

            Vector3f p2 = Vector3f.add(pos1, (Vector3f)Vector3f.sub(pos2, pos1, null).scale(0.5f), null);
            Vector3f p3 = Vector3f.add(pos1, (Vector3f)Vector3f.sub(pos3, pos1, null).scale(0.5f), null);
            Vector3f p4 = Vector3f.add(pos1, (Vector3f)Vector3f.sub(pos4, pos1, null).scale(0.5f), null);

            children = new QuadSphereChunk[4];
            children[0] = new QuadSphereChunk(parent, pos1, newsize, orientation);
            children[1] = new QuadSphereChunk(parent, p2, newsize, orientation);
            children[2] = new QuadSphereChunk(parent, p3, newsize, orientation);
            children[3] = new QuadSphereChunk(parent, p4, newsize, orientation);
        }
    }

Then there is also a factory to generate textures on a que.
As you can see an face only splits 4 times = 16 quads drawn, the texture fixes the rest.

Wow. That’s nearly the same way I am doing it, except that I hardcoded a lot of vector functions, AND I am storing things differently.

The code I wrote up originally (and I didn’t touch it for weeks now) would probably make a good coders eyes crying blood. And thats not a joke.
It’s time to clean my code up, then I need to get the terrain-patches to actually render.

- Longor1996

Your welcome! Glad I could help!

And this is how it looks like when I render all the points as… well… GL_POINTS.

http://abload.de/img/arbitraty4pointgridmemoj1l.png

Have a nice day!

  • Longor1996