# Procedural Planet Terrain Engine

Howdy!

Back again, this time I’m working on a Procedural planet generator. So far I’ve been able to get a Subdivided Plane converted into a sphere (victory!) but am hitting some snags with the UV side of the house. I more or less understand the concept…I think. I essentially need the four corners of a subdivided square, but I’m not sure how that then gets applied to the pieces composing the quad face…

Attached is a screen grab of what I’ve got and the code as it sits right now. The textures are manually applied because I’ve been focusing on getting the geometry right. So the UV coordinates per patch object are working great, so I know I at least haven’t screwed those up to bad. Now I just need each face to be one continuous UV space, which will then be mapped with a Cube Map…and procedural textures.

Yeah, so…basically: I need help turning 4 (or however many sub-divisions the user has) into 1 large UV space instead of 4 or 6 or 8 or 3 or whatever individual UV spaces.

Code!

``````using UnityEngine;
using System.Collections.Generic;
using System;

public class ProceduralPlanet : MonoBehaviour
{
//Define the number of Subdivions for patch side.
public int patchSubdivisions = 1;
//Define the number of vertices per of each patch segment.
public int patchResolution = 2;

public Vector3[] patchVertices;

private int patchID = 0;
private int vertIndex = 0;

public enum CubeIndex { TOP, BOTTOM, FRONT, BACK, LEFT, RIGHT }

public List<SurfacePatch> surfacePatches = new List<SurfacePatch>();

void Start()
{
patchVertices = new Vector3[((patchSubdivisions * patchSubdivisions) * 4) * 6];
// First we have define our dimensions:
//          +Z
//   -1,1    |    1,1
//      +----|----+
//      |    |    |
//-X --------0-------- +X
//      |    |    |
//      +----|----+
//  -1,-1    |    1,-1
//          -Z
//
// First we have to figure out what our distance increment is going to be. Since we
// know that our total width is 2 we can figure that out using the following:
float increment = 2.0f / patchSubdivisions;
// We can then find the patchHalfWidth using the following:
float patchHalfWidth = (patchSubdivisions / 2.0f) * increment;
//Next we plot the vertices for all of the sub-patches:
if (patchSubdivisions > 0)
{
//Loop through the 'z' positions
for (int z = 0; z < patchSubdivisions; z++)
{
//Each time through we define the starting 'z' position for each sub-patch and the
//next 'z' position of the sub-patch.
float _z  = z * increment;
float _z1 = (z + 1) * increment;

for (int x = 0; x < patchSubdivisions; x++)
{
//Each time through we define the starting 'z' position for each sub-patch and the
//next 'z' position of the sub-patch.
float _x  = x * increment;
float _x1 = (x + 1) * increment;

//Pass our generated data to CreateSubPatch which then defines the coordinates for
//each side of the Cube.
for (int i = 0; i < 6; i++)
{
CreatePatch(patchHalfWidth, _x, _x1, _z, _z1, (CubeIndex)i, x, z);
}
}
}
}
}

void CreatePatch(float half, float x, float x1, float z, float z1,  CubeIndex index, int sX, int sZ)
{
//A Vector Array to hold our Patch Vertices...
Vector3 lowLeft  = Vector3.zero;
Vector3 topLeft  = Vector3.zero;
Vector3 topRight = Vector3.zero;
Vector3 lowRight = Vector3.zero;

switch (index)
{
case CubeIndex.TOP:
lowLeft = new Vector3(x - half, half, z - half);
topLeft = new Vector3(x - half, half, z1 - half);
topRight = new Vector3(x1 - half, half, z1 - half);
lowRight = new Vector3(x1 - half, half, z - half);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;

case CubeIndex.BOTTOM:
lowLeft = new Vector3(x - half, -half, z - half);
topLeft = new Vector3(x - half, -half, z1 - half);
topRight = new Vector3(x1 - half, -half, z1 - half);
lowRight = new Vector3(x1 - half, -half, z - half);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;

case CubeIndex.FRONT:
lowLeft = new Vector3(x - half, half - x1, half);
topLeft = new Vector3(x - half, half - z, half);
topRight = new Vector3(x1 - half, half - z, half);
lowRight = new Vector3(x1 - half, half - z1, half);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;

case CubeIndex.BACK:
lowLeft = new Vector3(x - half, half - x1, -half);
topLeft = new Vector3(x - half, half - z, -half);
topRight = new Vector3(x1 - half, half - z, -half);
lowRight = new Vector3(x1 - half, half - z1, -half);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;

case CubeIndex.LEFT:
lowLeft = new Vector3(-half, half - x1, -half + x);
topLeft = new Vector3(-half, half - z, -half + x);
topRight = new Vector3(-half, half - z, -half + x1);
lowRight = new Vector3(-half, half - z1, -half + x1);

patchVertices[vertIndex] = lowLeft;
patchVertices[vertIndex + 1] = topLeft;
patchVertices[vertIndex + 2] = topRight;
patchVertices[vertIndex + 3] = lowRight;
break;

case CubeIndex.RIGHT:
lowLeft = new Vector3(half, half - x1, -half + x);
topLeft = new Vector3(half, half - z, -half + x);
topRight = new Vector3(half, half - z, -half + x1);
lowRight = new Vector3(half, half - z1, -half + x1);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;
}

CreatePatchObject(patchID, lowLeft, topLeft, topRight, lowRight, index, sX, sZ);
//Increment the patchID number after the sub-patch has been created.
patchID++;
vertIndex += 4;
}

void CreatePatchObject(int id, Vector3 lowLeft, Vector3 topLeft, Vector3 topRight, Vector3 lowRight,  CubeIndex index, int sX, int sZ)
{
GameObject patchObject = new GameObject(id + " " + index + " PatchObject (" + sX + " , " + sZ + ")");
patchObject.layer = gameObject.layer;
patchObject.tag = gameObject.tag;
patchObject.transform.parent = transform;
patchObject.transform.position = transform.position;

patch.InitPatchGenerator(lowLeft, topLeft, topRight, lowRight, this, index, null, sX, sZ);
patch.GeneratePatchCoordinates(patchSubdivisions, patchResolution);
}

/*private void OnDrawGizmos ()
{
if (patchVertices == null)
{
return;
}

for (int i = 0; i < patchVertices.Length; i++)
{
Gizmos.color = Color.black;
Gizmos.DrawWireSphere(patchVertices[i], 0.0625f);
}
//Gizmos.color = Color.yellow;
//Gizmos.DrawRay(vertices[i], normals[i]);
}*/
}
``````
``````using UnityEngine;
using System.Collections;

public class SurfacePatch : MonoBehaviour
{
public Vector3 _lowLeft;
public Vector3 _topLeft;
public Vector3 _topRight;
public Vector3 _lowRight;

public Mesh patch;

public ProceduralPlanet _planet;
public SurfacePatch _parent;
public ProceduralPlanet.CubeIndex _index;
public int _sX, _sZ;

float patchWidth;
private Vector3[] patchVertices;

private int vertIndex = 0;
private int[] vertBuffer;
private Vector2[] patchUV;
private Vector3[] patchNormals;

public void InitPatchGenerator(Vector3 lowLeft, Vector3 topLeft, Vector3 topRight, Vector3 lowRight, ProceduralPlanet planet, ProceduralPlanet.CubeIndex index, SurfacePatch parent, int sX, int sZ)
{
_planet = planet;
_index = index;
_parent = parent;
_sX = sX;
_sZ = sZ;

_lowLeft = lowLeft;
_topLeft = topLeft;
_topRight = topRight;
_lowRight = lowRight;

if (patch == null)
{
patch = meshFilter.sharedMesh = new Mesh();

GetComponent<Renderer>().enabled = true;
}
}

public void GeneratePatchCoordinates(int patchSubdivisions, int patchResolution)
{
patchVertices = new Vector3[patchResolution * patchResolution];
vertBuffer = new int[(patchResolution - 1) * (patchResolution - 1) * 6];
patchUV = new Vector2[patchVertices.Length];
patchNormals = new Vector3[patchVertices.Length];

if (_index == ProceduralPlanet.CubeIndex.FRONT || _index == ProceduralPlanet.CubeIndex.BACK || _index == ProceduralPlanet.CubeIndex.TOP || _index == ProceduralPlanet.CubeIndex.BOTTOM)
{
patchWidth = _topRight.x - _lowLeft.x;
}

if (_index == ProceduralPlanet.CubeIndex.LEFT || _index == ProceduralPlanet.CubeIndex.RIGHT)
{
patchWidth = _topRight.z - _lowLeft.z;
}

float increment = patchWidth / (patchResolution - 1);
float patchHalfWidth = ((patchResolution - 1) / patchWidth) * increment;

for (int z = 0; z < patchResolution; z++)
{
float _z = z * increment;
float _z1 = (z + 1) * increment;

for (int x = 0; x < patchResolution; x++)
{
float _x = x * increment;
float _x1 = (x + 1) * increment;

CreatePatchMesh(patchHalfWidth, _x, _x1, _z, _z1, patchSubdivisions, patchResolution);
}
}
}

private void CreatePatchMesh(float half, float x, float x1, float z, float z1, int subDiv, int resolution)
{
float xIncrement = ((float)_sX / (float)subDiv) * 2;
float zIncrement = ((float)_sZ / (float)subDiv) * 2;

switch(_index)
{
case ProceduralPlanet.CubeIndex.TOP:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, _lowLeft.y, z - half + zIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);
//Assign UV coordinates
patchUV[vertIndex] = new Vector2(x, z);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi + 1;

vertBuffer[ti + 3] = vi + resolution;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + 1;
}
}
break;

case ProceduralPlanet.CubeIndex.BOTTOM:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, _lowLeft.y, z - half + zIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);
//Assign UV coordinates
patchUV[vertIndex] = new Vector2(x, z);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi + 1;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi;

vertBuffer[ti + 3] = vi + 1;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + resolution;
}
}
break;

case ProceduralPlanet.CubeIndex.FRONT:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, z - half + zIncrement, _lowLeft.z);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);
//Assign UV coordinates
patchUV[vertIndex] = new Vector2(x, z);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi + 1;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi;

vertBuffer[ti + 3] = vi + 1;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + resolution;
}
}
break;

case ProceduralPlanet.CubeIndex.BACK:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, z - half + zIncrement, _lowLeft.z);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);
//Assign UV coordinates
patchUV[vertIndex] = new Vector2(x, z);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi + 1;

vertBuffer[ti + 3] = vi + resolution;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + 1;
}
}
break;

case ProceduralPlanet.CubeIndex.LEFT:
patchVertices[vertIndex] = new Vector3(_lowLeft.x, x - half + zIncrement, -half + z + xIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);
//Assign UV coordinates
patchUV[vertIndex] = new Vector2(x, z);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi + 1;

vertBuffer[ti + 3] = vi + resolution;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + 1;
}
}
break;

case ProceduralPlanet.CubeIndex.RIGHT:
patchVertices[vertIndex] = new Vector3(_lowLeft.x, x - half + zIncrement, -half + z + xIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);
//Assign UV coordinates
patchUV[vertIndex] = new Vector2(x, z);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi + 1;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi;

vertBuffer[ti + 3] = vi + 1;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + resolution;
}
}
break;
}

patch.vertices = patchVertices;
patch.uv = patchUV;
patch.triangles = vertBuffer;
patch.RecalculateNormals();

vertIndex++;
}

{

}

public static Vector3 MapToSphere(Vector3 point)
{
float dX2, dY2, dZ2;
float dX2Half, dY2Half, dZ2Half;

dX2 = point.x * point.x;
dY2 = point.y * point.y;
dZ2 = point.z * point.z;

dX2Half = dX2 * 0.5f;
dY2Half = dY2 * 0.5f;
dZ2Half = dZ2 * 0.5f;

point.x = point.x * Mathf.Sqrt(1f - dY2Half - dZ2Half + (dY2 * dZ2) * (1f / 3f));
point.y = point.y * Mathf.Sqrt(1f - dZ2Half - dX2Half + (dZ2 * dX2) * (1f / 3f));
point.z = point.z * Mathf.Sqrt(1f - dX2Half - dY2Half + (dX2 * dY2) * (1f / 3f));

return point;
}

/*private void OnDrawGizmos()
{
if (patchVertices == null)
{
return;
}

for (int i = 0; i < patchVertices.Length; i++)
{
if (_index == ProceduralPlanet.CubeIndex.TOP)
{
Gizmos.color = Color.red;
}

if (_index == ProceduralPlanet.CubeIndex.BOTTOM)
{
Gizmos.color = Color.magenta;
}

if (_index == ProceduralPlanet.CubeIndex.FRONT)
{
Gizmos.color = Color.green;
}

if (_index == ProceduralPlanet.CubeIndex.BACK)
{
Gizmos.color = Color.blue;
}

Gizmos.DrawSphere(patchVertices[i], 0.0125f);
}
//Gizmos.color = Color.yellow;
//Gizmos.DrawRay(vertices[i], normals[i]);
}*/
}
``````

V/R

1 Like

Forgot…the relevant code is located in the CreatePatchMesh function in the case statements.

Well, good news! I solved the UV issue I was having, but now I’m having issues applying the perlin noise as a height modifier to my vertices…Here is a screen shot of what’s going on and also the most current code. Any help would be awesome! I suspect the issue lies in how I’m handling stepping through the texture to find the height info…The first tile on the top part of the sphere has zero deformation and then the tile to the right of it looks kind of right…but not really. You can see the craziness of the other tiles as well. Any help sorting this out would be great!

``````using UnityEngine;
using System.Collections.Generic;
using System;
using LibNoise;
using LibNoise.Generator;
using LibNoise.Operator;

public class ProceduralPlanet : MonoBehaviour
{
//Define the number of Subdivions for patch side.
public int patchSubdivisions = 1;
//Define the number of vertices per of each patch segment.
public int patchResolution = 2;

public float xOrg = 10.0f;
public float yOrg = 10.0f;
public float scale = 1.0f;
public int textureSize = 128;

//public Vector3[] patchVertices;

private int patchID = 0;
//private int vertIndex = 0;

public enum CubeIndex { TOP, BOTTOM, FRONT, BACK, LEFT, RIGHT }

private Material baseMaterial;
public Texture2D[] textures = new Texture2D[6];
Color[] pixelColor;

public List<SurfacePatch> surfacePatches = new List<SurfacePatch>();

void Start()
{
pixelColor = new Color[textureSize * textureSize];
//patchVertices = new Vector3[((patchSubdivisions * patchSubdivisions) * 4) * 6];
// First we have define our dimensions:
//          +Z
//   -1,1    |    1,1
//      +----|----+
//      |    |    |
//-X --------0-------- +X
//      |    |    |
//      +----|----+
//  -1,-1    |    1,-1
//          -Z
//
// First we have to figure out what our distance increment is going to be. Since we
// know that our total width is 2 we can figure that out using the following:
float increment = 2.0f / patchSubdivisions;
// We can then find the patchHalfWidth using the following:
float patchHalfWidth = (patchSubdivisions / 2.0f) * increment;
//Next we plot the vertices for all of the sub-patches:
if (patchSubdivisions > 0)
{
//Loop through the 'z' positions
for (int z = 0; z < patchSubdivisions; z++)
{
//Each time through we define the starting 'z' position for each sub-patch and the
//next 'z' position of the sub-patch.
float _z  = z * increment;
float _z1 = (z + 1) * increment;

for (int x = 0; x < patchSubdivisions; x++)
{
//Each time through we define the starting 'z' position for each sub-patch and the
//next 'z' position of the sub-patch.
float _x  = x * increment;
float _x1 = (x + 1) * increment;

//Pass our generated data to CreateSubPatch which then defines the coordinates for
//each side of the Cube.
for (int i = 0; i < 6; i++)
{
CreatePatch(patchHalfWidth, _x, _x1, _z, _z1, (CubeIndex)i, x, z);
}
}
}
}
}

void CreatePatch(float half, float x, float x1, float z, float z1,  CubeIndex index, int sX, int sZ)
{
//A Vector Array to hold our Patch Vertices...
Vector3 lowLeft  = Vector3.zero;
Vector3 topLeft  = Vector3.zero;
Vector3 topRight = Vector3.zero;
Vector3 lowRight = Vector3.zero;

switch (index)
{
case CubeIndex.TOP:
lowLeft = new Vector3(x - half, half, z - half);
topLeft = new Vector3(x - half, half, z1 - half);
topRight = new Vector3(x1 - half, half, z1 - half);
lowRight = new Vector3(x1 - half, half, z - half);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;

case CubeIndex.BOTTOM:
lowLeft = new Vector3(x - half, -half, z - half);
topLeft = new Vector3(x - half, -half, z1 - half);
topRight = new Vector3(x1 - half, -half, z1 - half);
lowRight = new Vector3(x1 - half, -half, z - half);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;

case CubeIndex.FRONT:
lowLeft = new Vector3(x - half, half - x1, half);
topLeft = new Vector3(x - half, half - z, half);
topRight = new Vector3(x1 - half, half - z, half);
lowRight = new Vector3(x1 - half, half - z1, half);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;

case CubeIndex.BACK:
lowLeft = new Vector3(x - half, half - x1, -half);
topLeft = new Vector3(x - half, half - z, -half);
topRight = new Vector3(x1 - half, half - z, -half);
lowRight = new Vector3(x1 - half, half - z1, -half);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;

case CubeIndex.LEFT:
lowLeft = new Vector3(-half, half - x1, -half + x);
topLeft = new Vector3(-half, half - z, -half + x);
topRight = new Vector3(-half, half - z, -half + x1);
lowRight = new Vector3(-half, half - z1, -half + x1);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;

case CubeIndex.RIGHT:
lowLeft = new Vector3(half, half - x1, -half + x);
topLeft = new Vector3(half, half - z, -half + x);
topRight = new Vector3(half, half - z, -half + x1);
lowRight = new Vector3(half, half - z1, -half + x1);

//patchVertices[vertIndex] = lowLeft;
//patchVertices[vertIndex + 1] = topLeft;
//patchVertices[vertIndex + 2] = topRight;
//patchVertices[vertIndex + 3] = lowRight;
break;
}

CreatePatchObject(patchID, lowLeft, topLeft, topRight, lowRight, index, sX, sZ);
//Increment the patchID number after the sub-patch has been created.
patchID++;
//vertIndex += 4;
}

void CreatePatchObject(int id, Vector3 lowLeft, Vector3 topLeft, Vector3 topRight, Vector3 lowRight,  CubeIndex index, int sX, int sZ)
{
GameObject patchObject = new GameObject(id + " " + index + " PatchObject (" + sX + " , " + sZ + ")");
patchObject.layer = gameObject.layer;
patchObject.tag = gameObject.tag;
patchObject.transform.parent = transform;
patchObject.transform.position = transform.position;
//Add a MeshRenderer to access Renderer functions
//patchObject.GetComponent<Renderer>().material = base.GetComponent<Renderer>().material;
//Add a MeshCollider for Collision purposes

for (int i = 0; i < 6; i++)
{
textures[i] = new Texture2D(textureSize, textureSize);

float y = 0.0f;
while (y < textureSize)
{
float x = 0.0f;
while (x < textureSize)
{
float xCoord = xOrg + x / textureSize * scale;
float yCoord = xOrg + y / textureSize * scale;
float sample = Mathf.PerlinNoise(xCoord, yCoord);
pixelColor[(int)y * textureSize + (int)x] = new Color(sample, sample, sample);
x++;
}
y++;
}
textures[i].SetPixels(pixelColor);
textures[i].Apply();
}

switch (index)
{
case CubeIndex.TOP:
patchObject.gameObject.GetComponent<Renderer>().material.mainTexture = textures[0];
break;

case CubeIndex.BOTTOM:
patchObject.gameObject.GetComponent<Renderer>().material.mainTexture = textures[1];
break;

case CubeIndex.FRONT:
patchObject.gameObject.GetComponent<Renderer>().material.mainTexture = textures[2];
break;

case CubeIndex.BACK:
patchObject.gameObject.GetComponent<Renderer>().material.mainTexture = textures[3];
break;

case CubeIndex.LEFT:
patchObject.gameObject.GetComponent<Renderer>().material.mainTexture = textures[4];
break;

case CubeIndex.RIGHT:
patchObject.gameObject.GetComponent<Renderer>().material.mainTexture = textures[5];
break;
}

//Initialize the patch generator with the coordinates generated in Start
patch.InitPatchGenerator(lowLeft, topLeft, topRight, lowRight, this, index, null, sX, sZ);
//Generate the coordinates for the patch based on the sub-division level and resolution
patch.GeneratePatchCoordinates(patchSubdivisions, patchResolution);
//After the Patch has been created, assign that patch to this objects MeshCollider
patchObject.GetComponent<MeshCollider>().sharedMesh = patch.ReturnMesh();
}

/*private void OnDrawGizmos ()
{
if (patchVertices == null)
{
return;
}

for (int i = 0; i < patchVertices.Length; i++)
{
Gizmos.color = Color.black;
Gizmos.DrawWireSphere(patchVertices[i], 0.0625f);
}
//Gizmos.color = Color.yellow;
//Gizmos.DrawRay(vertices[i], normals[i]);
}*/
}
``````
``````using UnityEngine;
using System.Collections;

public class SurfacePatch : MonoBehaviour
{
private Vector3 _lowLeft;
private Vector3 _topLeft;
private Vector3 _topRight;
private Vector3 _lowRight;

private Mesh patch;

private ProceduralPlanet _planet;
private SurfacePatch _parent;
private ProceduralPlanet.CubeIndex _index;
private int _sX, _sZ;

private float patchWidth;
private Vector3[] patchVertices;

private int vertIndex = 0;
private int[] vertBuffer;
private Vector2[] patchUV;
private Vector3[] patchNormals;

private float uvResolution;
private float uvXIncrement;
private float uvZIncrement;

int k = 0;
int l = 0;

public void InitPatchGenerator(Vector3 lowLeft, Vector3 topLeft, Vector3 topRight, Vector3 lowRight, ProceduralPlanet planet, ProceduralPlanet.CubeIndex index, SurfacePatch parent, int sX, int sZ)
{
_planet = planet;
_index = index;
_parent = parent;
_sX = sX;
_sZ = sZ;

_lowLeft = lowLeft;
_topLeft = topLeft;
_topRight = topRight;
_lowRight = lowRight;

if (patch == null)
{
patch = meshFilter.sharedMesh = new Mesh();

GetComponent<Renderer>().enabled = true;
}
}

public void GeneratePatchCoordinates(int patchSubdivisions, int patchResolution)
{
patchVertices = new Vector3[patchResolution * patchResolution];
vertBuffer = new int[(patchResolution - 1) * (patchResolution - 1) * 6];
patchUV = new Vector2[patchVertices.Length];
patchNormals = new Vector3[patchVertices.Length];

if (_index == ProceduralPlanet.CubeIndex.FRONT || _index == ProceduralPlanet.CubeIndex.BACK || _index == ProceduralPlanet.CubeIndex.TOP || _index == ProceduralPlanet.CubeIndex.BOTTOM)
{
patchWidth = _topRight.x - _lowLeft.x;
}

if (_index == ProceduralPlanet.CubeIndex.LEFT || _index == ProceduralPlanet.CubeIndex.RIGHT)
{
patchWidth = _topRight.z - _lowLeft.z;
}

float increment = patchWidth / (patchResolution - 1);
float patchHalfWidth = ((patchResolution - 1) / patchWidth) * increment;

for (int z = 0; z < patchResolution; z++)
{
float _z = z * increment;
float _z1 = (z + 1) * increment;

for (int x = 0; x < patchResolution; x++)
{
float _x = x * increment;
float _x1 = (x + 1) * increment;

CreatePatchMesh(patchHalfWidth, _x, _x1, _z, _z1, patchSubdivisions, patchResolution);
}
}
}

private void CreatePatchMesh(float half, float x, float x1, float z, float z1, int subDiv, int resolution)
{
float xIncrement = ((float)_sX / (float)subDiv) * 2;
float zIncrement = ((float)_sZ / (float)subDiv) * 2;

uvXIncrement = _sX * (1.0f / subDiv);
uvZIncrement = _sZ * (1.0f / subDiv);

int texXIncrement = _sX * ((_planet.textureSize / subDiv) / resolution);
int texZIncrement = _sZ * ((_planet.textureSize / subDiv) / resolution);
float noise = 0;

switch(_index)
{
case ProceduralPlanet.CubeIndex.TOP:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, _lowLeft.y, z - half + zIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

patchUV[vertIndex] = new Vector2((x / 2f) + uvXIncrement , (z / 2f) + uvZIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi + 1;

vertBuffer[ti + 3] = vi + resolution;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + 1;
}
}
noise = _planet.textures[0].GetPixel(l * texXIncrement, k * texZIncrement).grayscale * 0.5f;
//noise = Mathf.Clamp(noise, (_planet.planetRadius + noise + 1.0f), _planet.planetRadius + noise + 1.0f);

patch.name = "TOP (" + _sX + "," + _sZ + ")";
break;

case ProceduralPlanet.CubeIndex.BOTTOM:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, _lowLeft.y, z - half + zIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

//Assign UV coordinates
patchUV[vertIndex] = new Vector2((x / 2f) + uvXIncrement, (z / 2f) + uvZIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi + 1;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi;

vertBuffer[ti + 3] = vi + 1;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + resolution;
}
}
noise = _planet.textures[0].GetPixel(k * texXIncrement, l * texZIncrement).grayscale * 0.5f;
//noise = Mathf.Clamp(noise, _planet.planetWaterLevel * (_planet.planetRadius + noise + 1.0f), _planet.planetRadius + noise + 1.0f);

patch.name = "BOTTOM (" + _sX + "," + _sZ + ")";
break;

case ProceduralPlanet.CubeIndex.FRONT:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, z - half + zIncrement, _lowLeft.z);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

//Assign UV coordinates
patchUV[vertIndex] = new Vector2((x / 2f) + uvXIncrement, (z / 2f) + uvZIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi + 1;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi;

vertBuffer[ti + 3] = vi + 1;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + resolution;
}
}
noise = _planet.textures[0].GetPixel(k * texXIncrement, l * texZIncrement).grayscale * 0.5f;
//noise = Mathf.Clamp(noise, _planet.planetWaterLevel * (_planet.planetRadius + noise + 1.0f), _planet.planetRadius + noise + 1.0f);

patch.name = "FRONT (" + _sX + "," + _sZ + ")";
break;

case ProceduralPlanet.CubeIndex.BACK:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, z - half + zIncrement, _lowLeft.z);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

//Assign UV coordinates
patchUV[vertIndex] = new Vector2((x / 2f) + uvXIncrement, (z / 2f) + uvZIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi + 1;

vertBuffer[ti + 3] = vi + resolution;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + 1;
}
}
noise = _planet.textures[0].GetPixel(k * texXIncrement, l * texZIncrement).grayscale;

patch.name = "BACK (" + _sX + "," + _sZ + ")";
break;

case ProceduralPlanet.CubeIndex.LEFT:
patchVertices[vertIndex] = new Vector3(_lowLeft.x, x - half + zIncrement, -half + z + xIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

//Assign UV coordinates
patchUV[vertIndex] = new Vector2((x / 2f) + uvZIncrement, (z / 2f) + uvXIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi + 1;

vertBuffer[ti + 3] = vi + resolution;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + 1;
}
}
noise = _planet.textures[0].GetPixel(k * texXIncrement, l * texZIncrement).grayscale * 0.5f;
//noise = Mathf.Clamp(noise, _planet.planetWaterLevel * (_planet.planetRadius + noise + 1.0f), _planet.planetRadius + noise + 1.0f);

patch.name = "LEFT (" + _sX + "," + _sZ + ")";
break;

case ProceduralPlanet.CubeIndex.RIGHT:
patchVertices[vertIndex] = new Vector3(_lowLeft.x, x - half + zIncrement, -half + z + xIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

//Assign UV coordinates
patchUV[vertIndex] = new Vector2((x / 2f) + uvZIncrement, (z / 2f) + uvXIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi + 1;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi;

vertBuffer[ti + 3] = vi + 1;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + resolution;
}
}
noise = _planet.textures[0].GetPixel(k * texXIncrement, l * texZIncrement).grayscale * 2.5f;
//noise = Mathf.Clamp(noise, _planet.planetWaterLevel * (_planet.planetRadius + noise + 1.0f), _planet.planetRadius + noise + 1.0f);

patch.name = "RIGHT (" + _sX + "," + _sZ + ")";
break;
}

patch.vertices = patchVertices;
patch.uv = patchUV;
patch.triangles = vertBuffer;
patch.RecalculateNormals();

vertIndex++;
k++;
l++;
}

{

}

public Mesh ReturnMesh()
{
return patch;
}

public static Vector3 MapToSphere(Vector3 point)
{
float dX2, dY2, dZ2;
float dX2Half, dY2Half, dZ2Half;

dX2 = point.x * point.x;
dY2 = point.y * point.y;
dZ2 = point.z * point.z;

dX2Half = dX2 * 0.5f;
dY2Half = dY2 * 0.5f;
dZ2Half = dZ2 * 0.5f;

point.x = point.x * Mathf.Sqrt(1f - dY2Half - dZ2Half + (dY2 * dZ2) * (1f / 3f));
point.y = point.y * Mathf.Sqrt(1f - dZ2Half - dX2Half + (dZ2 * dX2) * (1f / 3f));
point.z = point.z * Mathf.Sqrt(1f - dX2Half - dY2Half + (dX2 * dY2) * (1f / 3f));

return point;
}

/*private void OnDrawGizmos()
{
if (patchVertices == null)
{
return;
}

for (int i = 0; i < patchVertices.Length; i++)
{
if (_index == ProceduralPlanet.CubeIndex.TOP)
{
Gizmos.color = Color.red;
}

if (_index == ProceduralPlanet.CubeIndex.BOTTOM)
{
Gizmos.color = Color.magenta;
}

if (_index == ProceduralPlanet.CubeIndex.FRONT)
{
Gizmos.color = Color.green;
}

if (_index == ProceduralPlanet.CubeIndex.BACK)
{
Gizmos.color = Color.blue;
}

Gizmos.DrawSphere(patchVertices[i], 0.0125f);
}
//Gizmos.color = Color.yellow;
//Gizmos.DrawRay(vertices[i], normals[i]);
}*/
}
``````

1 Like

Bump…

So this is what I’ve got implemented so far:

``````        int texXIncrement = _sX * ((_planet.textureSize / subDiv) / resolution);
int texZIncrement = _sZ * ((_planet.textureSize / subDiv) / resolution);
float noise = 0;
``````
``````                noise = _planet.textures[0].GetPixel(l * texXIncrement, k * texZIncrement).grayscale * 0.5f;
//noise = Mathf.Clamp(noise, (_planet.planetRadius + noise + 1.0f), _planet.planetRadius + noise + 1.0f);
``````
``````        patch.vertices = patchVertices;
patch.uv = patchUV;
patch.triangles = vertBuffer;
patch.RecalculateNormals();
vertIndex++;
k++;
l++;
``````

The idea is that the texture covers one full side of the cube that makes up the sphere. It’s resolution is defined by the user. Since the texture is a fixed size, I figure out how many pixels each vertex needs to move and sample. So that takes into account first the number of subdivisions currently implemented and then the number of vertices per subdivision surface. This is then put into GetPixel. Each time through the switch it then increments the current vertex…But from looking at the results it’s definitely not working as intended. Anyone see anything glaringly obvious in the code?

1 Like

I made the following adjustments to my code and got the following:

So…i’m a little bit closer, now I just need to get the sides of each patch to line up…at least the depth corresponds to the height map now…here’s the relevant lines of code updated…if anyone has any ideas what I’m still doing wrong, please chime in!

``````    private void CreatePatchMesh(float half, float x, float x1, float z, float z1, int subDiv, int resolution)
{
float xIncrement = ((float)_sX / (float)subDiv) * 2;
float zIncrement = ((float)_sZ / (float)subDiv) * 2;

uvXIncrement = _sX * (1.0f / subDiv);
uvZIncrement = _sZ * (1.0f / subDiv);

//int texXIncrement = _sX * ((_planet.textureSize / subDiv) / resolution);
//int texZIncrement = _sZ * ((_planet.textureSize / subDiv) / resolution);
int texXIncrement = ((_planet.textureSize / subDiv) / resolution);
int texZIncrement = ((_planet.textureSize / subDiv) / resolution);
float[] noise = new float[patchVertices.Length];

switch(_index)
{
case ProceduralPlanet.CubeIndex.TOP:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, _lowLeft.y, z - half + zIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);
//patchVertices[vertIndex].Normalize();

patchUV[vertIndex] = new Vector2((x / 2f) + uvXIncrement , (z / 2f) + uvZIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi + 1;

vertBuffer[ti + 3] = vi + resolution;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + 1;
}
}
for (int a = 0, b = 0; a < resolution; a++)
{
for (int c = 0; c < resolution; c++, b++)
{
noise[b] = _planet.textures[0].GetPixel(c * texXIncrement, a * texZIncrement).grayscale;
//noise[b] = Mathf.Clamp(noise[b], _planet.planetRadius + noise[b] + 1.0f, _planet.planetRadius + noise[b] + 1.0f);
}
}

patch.name = "TOP (" + _sX + "," + _sZ + ")";
break;
``````
1 Like

So no one has any insight into why this isn’t working as it’s supposed to? I’ve double checked my math using excel to ensure everything is correct implementation wise…but I’m still getting disjointed patches. Here’s the most up to date code:

``````using UnityEngine;
using System.Collections;

public class SurfacePatch : MonoBehaviour
{
private Vector3 _lowLeft;
private Vector3 _topLeft;
private Vector3 _topRight;
private Vector3 _lowRight;

private Mesh patch;

private ProceduralPlanet _planet;
private SurfacePatch _parent;
private ProceduralPlanet.CubeIndex _index;
private int _sX, _sZ;

private float patchWidth;
private Vector3[] patchVertices;

private int vertIndex = 0;
private int[] vertBuffer;
private Vector2[] patchUV;
private Vector3[] patchNormals;

private float uvResolution;
private float uvXIncrement;
private float uvZIncrement;
public float[] noise;

public void InitPatchGenerator(Vector3 lowLeft, Vector3 topLeft, Vector3 topRight, Vector3 lowRight, ProceduralPlanet planet, ProceduralPlanet.CubeIndex index, SurfacePatch parent, int sX, int sZ)
{
_planet = planet;
_index = index;
_parent = parent;
_sX = sX;
_sZ = sZ;

_lowLeft = lowLeft;
_topLeft = topLeft;
_topRight = topRight;
_lowRight = lowRight;

if (patch == null)
{
patch = meshFilter.sharedMesh = new Mesh();

GetComponent<Renderer>().enabled = true;
}
}

public void GeneratePatchCoordinates(int patchSubdivisions, int patchResolution)
{
patchVertices = new Vector3[patchResolution * patchResolution];
vertBuffer = new int[(patchResolution - 1) * (patchResolution - 1) * 6];
patchUV = new Vector2[patchVertices.Length];
patchNormals = new Vector3[patchVertices.Length];
noise = new float[(patchResolution + 1) * (patchResolution + 1)];

if (_index == ProceduralPlanet.CubeIndex.FRONT || _index == ProceduralPlanet.CubeIndex.BACK || _index == ProceduralPlanet.CubeIndex.TOP || _index == ProceduralPlanet.CubeIndex.BOTTOM)
{
patchWidth = _topRight.x - _lowLeft.x;
}

if (_index == ProceduralPlanet.CubeIndex.LEFT || _index == ProceduralPlanet.CubeIndex.RIGHT)
{
patchWidth = _topRight.z - _lowLeft.z;
}

float increment = patchWidth / (patchResolution - 1);
float patchHalfWidth = ((patchResolution - 1) / patchWidth) * increment;

for (int z = 0; z < patchResolution; z++)
{
float _z = z * increment;
float _z1 = (z + 1) * increment;

for (int x = 0; x < patchResolution; x++)
{
float _x = x * increment;
float _x1 = (x + 1) * increment;

CreatePatchMesh(patchHalfWidth, _x, _x1, _z, _z1, patchSubdivisions, patchResolution);
}
}
}

private void CreatePatchMesh(float half, float x, float x1, float z, float z1, int subDiv, int resolution)
{
float xIncrement = ((float)_sX / (float)subDiv) * 2;
float zIncrement = ((float)_sZ / (float)subDiv) * 2;

uvXIncrement = _sX * (1.0f / subDiv);
uvZIncrement = _sZ * (1.0f / subDiv);

//int texXIncrement = _sX * ((_planet.textureSize / subDiv) / resolution);
//int texZIncrement = _sZ * ((_planet.textureSize / subDiv) / resolution);
int texXIncrement = ((_planet.textureSize / subDiv) / resolution);
int texZIncrement = ((_planet.textureSize / subDiv) / resolution);

switch(_index)
{
case ProceduralPlanet.CubeIndex.TOP:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, _lowLeft.y, z - half + zIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);
//patchVertices[vertIndex].Normalize();

patchUV[vertIndex] = new Vector2((x / 2f) + uvXIncrement , (z / 2f) + uvZIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi + 1;

vertBuffer[ti + 3] = vi + resolution;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + 1;
}
}

for (int a = 0, b = 0; a <= resolution; a++)
{
for (int c = 0; c <= resolution; c++, b++)
{
noise[b] = _planet.textures[0].GetPixel(c * texXIncrement + ((_sX * resolution) * texXIncrement), a * texZIncrement + ((_sZ * resolution) * texZIncrement)).grayscale;
//noise[b] = Mathf.Clamp(noise[b], _planet.planetRadius + noise[b] + 1.0f, _planet.planetRadius + noise[b] + 1.0f);
}
}

patch.name = "TOP (" + _sX + "," + _sZ + ")";
break;

case ProceduralPlanet.CubeIndex.BOTTOM:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, _lowLeft.y, z - half + zIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

//Assign UV coordinates
patchUV[vertIndex] = new Vector2((x / 2f) + uvXIncrement, (z / 2f) + uvZIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi + 1;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi;

vertBuffer[ti + 3] = vi + 1;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + resolution;
}
}

for (int a = 0, b = 0; a < resolution; a++)
{
for (int c = 0; c < resolution; c++, b++)
{
noise[b] = _planet.textures[0].GetPixel(c * texXIncrement, a * texZIncrement).grayscale;
//noise[b] = Mathf.Clamp(noise[b], _planet.planetRadius + noise[b] + 1.0f, _planet.planetRadius + noise[b] + 1.0f);
}
}

patch.name = "BOTTOM (" + _sX + "," + _sZ + ")";
break;

case ProceduralPlanet.CubeIndex.FRONT:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, z - half + zIncrement, _lowLeft.z);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

//Assign UV coordinates
patchUV[vertIndex] = new Vector2((x / 2f) + uvXIncrement, (z / 2f) + uvZIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi + 1;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi;

vertBuffer[ti + 3] = vi + 1;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + resolution;
}
}

for (int a = 0, b = 0; a < resolution; a++)
{
for (int c = 0; c < resolution; c++, b++)
{
noise[b] = _planet.textures[0].GetPixel(c * texXIncrement, a * texZIncrement).grayscale;
//noise[b] = Mathf.Clamp(noise[b], _planet.planetRadius + noise[b] + 1.0f, _planet.planetRadius + noise[b] + 1.0f);
}
}

patch.name = "FRONT (" + _sX + "," + _sZ + ")";
break;

case ProceduralPlanet.CubeIndex.BACK:
patchVertices[vertIndex] = new Vector3(x - half + xIncrement, z - half + zIncrement, _lowLeft.z);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

//Assign UV coordinates
patchUV[vertIndex] = new Vector2((x / 2f) + uvXIncrement, (z / 2f) + uvZIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi + 1;

vertBuffer[ti + 3] = vi + resolution;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + 1;
}
}

for (int a = 0, b = 0; a < resolution; a++)
{
for (int c = 0; c < resolution; c++, b++)
{
noise[b] = _planet.textures[0].GetPixel(c * texXIncrement, a * texZIncrement).grayscale;
//noise[b] = Mathf.Clamp(noise[b], _planet.planetRadius + noise[b] + 1.0f, _planet.planetRadius + noise[b] + 1.0f);
}
}

patch.name = "BACK (" + _sX + "," + _sZ + ")";
break;

case ProceduralPlanet.CubeIndex.LEFT:
patchVertices[vertIndex] = new Vector3(_lowLeft.x, x - half + zIncrement, -half + z + xIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

//Assign UV coordinates
patchUV[vertIndex] = new Vector2((x / 2f) + uvZIncrement, (z / 2f) + uvXIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi + 1;

vertBuffer[ti + 3] = vi + resolution;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + 1;
}
}

for (int a = 0, b = 0; a < resolution; a++)
{
for (int c = 0; c < resolution; c++, b++)
{
noise[b] = _planet.textures[0].GetPixel(c * texXIncrement, a * texZIncrement).grayscale;
//noise[b] = Mathf.Clamp(noise[b], _planet.planetRadius + noise[b] + 1.0f, _planet.planetRadius + noise[b] + 1.0f);
}
}

patch.name = "LEFT (" + _sX + "," + _sZ + ")";
break;

case ProceduralPlanet.CubeIndex.RIGHT:
patchVertices[vertIndex] = new Vector3(_lowLeft.x, x - half + zIncrement, -half + z + xIncrement);
patchVertices[vertIndex] = MapToSphere(patchVertices[vertIndex]);

//Assign UV coordinates
patchUV[vertIndex] = new Vector2((x / 2f) + uvZIncrement, (z / 2f) + uvXIncrement);
//Draw the triangles
for (int ti = 0, vi = 0, i = 0; i < (resolution - 1); i++, vi++)
{
for (int j = 0; j < (resolution - 1); j++, ti += 6, vi++)
{
vertBuffer[ti] = vi + 1;
vertBuffer[ti + 1] = vi + resolution;
vertBuffer[ti + 2] = vi;

vertBuffer[ti + 3] = vi + 1;
vertBuffer[ti + 4] = vi + resolution + 1;
vertBuffer[ti + 5] = vi + resolution;
}
}

for (int a = 0, b = 0; a < resolution; a++)
{
for (int c = 0; c < resolution; c++, b++)
{
noise[b] = _planet.textures[0].GetPixel(c * texXIncrement, a * texZIncrement).grayscale;
//noise[b] = Mathf.Clamp(noise[b], _planet.planetRadius + noise[b] + 1.0f, _planet.planetRadius + noise[b] + 1.0f);
}
}

patch.name = "RIGHT (" + _sX + "," + _sZ + ")";
break;
}

patch.vertices = patchVertices;
patch.uv = patchUV;
patch.triangles = vertBuffer;
patch.RecalculateNormals();

vertIndex++;
}

{

}

public Mesh ReturnMesh()
{
return patch;
}

public static Vector3 MapToSphere(Vector3 point)
{
float dX2, dY2, dZ2;
float dX2Half, dY2Half, dZ2Half;

dX2 = point.x * point.x;
dY2 = point.y * point.y;
dZ2 = point.z * point.z;

dX2Half = dX2 * 0.5f;
dY2Half = dY2 * 0.5f;
dZ2Half = dZ2 * 0.5f;

point.x = point.x * Mathf.Sqrt(1f - dY2Half - dZ2Half + (dY2 * dZ2) * (1f / 3f));
point.y = point.y * Mathf.Sqrt(1f - dZ2Half - dX2Half + (dZ2 * dX2) * (1f / 3f));
point.z = point.z * Mathf.Sqrt(1f - dX2Half - dY2Half + (dX2 * dY2) * (1f / 3f));

return point;
}

/*private void OnDrawGizmos()
{
if (patchVertices == null)
{
return;
}

for (int i = 0; i < patchVertices.Length; i++)
{
if (_index == ProceduralPlanet.CubeIndex.TOP)
{
Gizmos.color = Color.red;
}

if (_index == ProceduralPlanet.CubeIndex.BOTTOM)
{
Gizmos.color = Color.magenta;
}

if (_index == ProceduralPlanet.CubeIndex.FRONT)
{
Gizmos.color = Color.green;
}

if (_index == ProceduralPlanet.CubeIndex.BACK)
{
Gizmos.color = Color.blue;
}

Gizmos.DrawSphere(patchVertices[i], 0.0125f);
}
//Gizmos.color = Color.yellow;
//Gizmos.DrawRay(vertices[i], normals[i]);
}*/
}
``````

Could the problem maybe lie in the way I’m mapping the points to the sphere? I’m pretty stuck at this point and out of ideas. Any help would be great, surely there’s a person smarter than I that sees something I don’t.

3 Likes

your mesh-fu is far beyond mine but it’s really good to see this get updated as you work things out

1 Like

Appreciate it! I actually re-wrote this beast using some threads I found in the forums as a guide. It seems to work a LOT better now. Haven’t implemented the height map displacements yet, but here be the new code. What I really need some major help with now is getting a seamless procedural cubemap implemented.

Anyone have some insights there?

Here’s the new code!

``````using UnityEngine;
using System.Collections;

using LibNoise;
using LibNoise.Generator;
using LibNoise.Operator;

public class NewTestPlanet : MonoBehaviour
{

//The number of Subdivions per Face
public int subDivisions = 2;
//The number of Vertices per Subdivision
public int vertsPerSubDivision = 4;

//Texture Data
public float xOrg = 1.0f;
public float yOrg = 1.0f;
public float scale = 10.0f;

public enum cubeIndex { TOP, BOTTOM, LEFT, RIGHT, FRONT, BACK }

public Texture2D[] textures;

void Start()
{
CreateSubdivisionSurfaces();
}

//Responsible for calling CreateSurface. Passes the following arguments to CreateSurface():
//  x-index (i)
//  z-index (j)
//  cube face (index)
void CreateSubdivisionSurfaces()
{
//For each face and for each Sub Division Surface, Create a new Surface
for (int index = 0; index < 6; index++)
{
for (int i = 0; i < subDivisions; i++)
{
for (int j = 0; j < subDivisions; j++)
{
GenerateSurfaceTextures(index);
GenerateSurfaceCoordinates(i, j, (cubeIndex)index);
}
}
}
}

//Creates the surfaces called by CreateSubdivisionSurfaces(). Creates a GameObject
//assigns a MeshRenderer and a MeshCollider to the created Game Object.
void GenerateSurfaceCoordinates(int xIndex, int zIndex, cubeIndex facing)
{
//Appropriately size (and create) the Vertex Array
Vector3[] vertices = new Vector3[vertsPerSubDivision * vertsPerSubDivision];
//Appropriately size (and create) the UV Array
Vector2[] uv  = new Vector2[vertices.Length];

//Calculate the increment to keep the vertices constrained to a 1x1 cube.
float increment = (1.0f / ((float)vertsPerSubDivision - 1)) / (float)subDivisions;
float uvIncrement = (1.0f / ((float)vertsPerSubDivision - 1)) / (float)subDivisions;

for (int i = 0, index = 0; i < vertsPerSubDivision; i++)
{
for (int j = 0; j < vertsPerSubDivision; j++, index++)
{
//Vertex Coordinates
float xPos = (float)j * increment - 0.5f + ((float)xIndex / (float)subDivisions);
float yPos = 0.5f;
float zPos = (float)i * increment - 0.5f + ((float)zIndex / (float)subDivisions);
//UV Coordinates
float xUV = (float)j * uvIncrement + ((float)xIndex / (float)subDivisions);
float zUV = (float)i * uvIncrement + ((float)zIndex / (float)subDivisions);

switch(facing)
{
case cubeIndex.TOP:
//Assign Vertex Coordinates
vertices[index] = new Vector3(xPos, yPos, zPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.BOTTOM:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-xPos, -yPos, zPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.LEFT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-yPos, zPos, -xPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.RIGHT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(yPos, zPos, xPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.FRONT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-xPos, zPos, yPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.BACK:
//Assign Vertex Coordinates
vertices[index] = new Vector3(xPos, zPos, -yPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;
}

//Spherize the Vertices
vertices[index] = vertices[index].normalized;
//Set the newly created sphere equal to the Radius
}
}
//Create the Surface Object
CreateSurfaceObject(vertices, uv, facing, xIndex, zIndex);
}

//Creates a new GameObject to which a MeshRenderer, MeshCollider and MeshFilter are attached.
//This is then passed to CreateMeshSurface where the actual Mesh is created.
void CreateSurfaceObject(Vector3[] vertexArray, Vector2[] uvArray, cubeIndex facing, int xIndex, int zIndex)
{
//Create a new GameObject
GameObject surface = new GameObject(facing + "Surface(" + xIndex + ", " + zIndex + ")");
surface.tag     = gameObject.tag;
surface.layer   = gameObject.layer;
surface.transform.parent    = transform;
surface.transform.position  = transform.position;

//Initialize the Renderer
surface.GetComponent<Renderer>().enabled = true;

//Assign the generated textures to the cube sphere
surface.GetComponent<Renderer>().material.mainTexture = textures[(int)facing];

//Create the Mesh
CreateSurfaceMesh(surface, vertexArray, uvArray, facing);
}

void CreateSurfaceMesh(GameObject surface, Vector3[] vertexArray, Vector2[] uvArray, cubeIndex facing)
{
//Create and size the Vertex Buffer
int vertBufferSize = vertsPerSubDivision - 1;
int[] vertexBuffer = new int[(vertBufferSize * vertBufferSize) * 6];
//Create a new Mesh object
Mesh surfaceMesh;
//Create a new Mesh using the Objects MeshFilter
surfaceMesh = surface.GetComponent<MeshFilter>().sharedMesh = new Mesh();
surfaceMesh.name = surface.name;

//Step through the Vertex Buffer
for (int triIndex = 0, vertIndex = 0, i = 0; i < vertBufferSize; i++, vertIndex++)
{
for (int j = 0; j < vertBufferSize; j++, triIndex += 6, vertIndex++)
{
//  1
//  | \
//  |  \
//  |   \
//  0----2
vertexBuffer[triIndex]      = vertIndex;
vertexBuffer[triIndex + 1]  = vertIndex + vertsPerSubDivision;
vertexBuffer[triIndex + 2]  = vertIndex + 1;
//  1----3
//    \  |
//     \ |
//      \|
//       2
vertexBuffer[triIndex + 3]  = vertIndex + vertsPerSubDivision;
vertexBuffer[triIndex + 4]  = vertIndex + vertsPerSubDivision + 1;
vertexBuffer[triIndex + 5]  = vertIndex + 1;
}
}
//Assign the Vertex Array from Generate Surface Coordinates to the Mesh Vertex Array
surfaceMesh.vertices = vertexArray;
//Assign the UV Coordinates
surfaceMesh.uv = uvArray;
//Assign the Vertex Buffer
surfaceMesh.triangles = vertexBuffer;
//Recalculate the Normals
surfaceMesh.RecalculateNormals();
//After the Mesh has been created, pass it back to the MeshCollider
surface.GetComponent<MeshCollider>().sharedMesh = surfaceMesh;
}

public void GenerateSurfaceTextures(int index)
{
//Texture size is equal to the number of sub divisions x the vertices per subdivision
int textureSize = subDivisions * vertsPerSubDivision;
//Create the texture and pixel color arrays
textures = new Texture2D[6];
Color[] pixelColor = new Color[textureSize * textureSize];

for (int i = 0; i < 6; i++)
{
textures[i] = new Texture2D(textureSize, textureSize);

float y = 0.0f;
while (y < textureSize)
{
float x = 0.0f;
while (x < textureSize)
{
float xCoord = xOrg + x / textureSize * scale;
float yCoord = xOrg + y / textureSize * scale;
float sample = Mathf.PerlinNoise(xCoord, yCoord);
pixelColor[(int)y * textureSize + (int)x] = new Color(sample, sample, sample);
x++;
}
y++;
}
textures[i].SetPixels(pixelColor);
textures[i].Apply();
}
}
}
``````

So cool! You are making your own Terrangen? What noise do you use?

Thanks, and right now just the default Unity Perlin.

So, here’s what I’ve more or less figured out for creating a cubemap with noise…

1. Determine what side of the cube map you’re going to start on
2. Using the Vertices that are making up the cube face, figure out where that vertex lies on the cube map face
3. Sample the above location to determine the ‘height’ of the noise

So I have to figure out the math to map a cube face point to my sphere…and maybe 3D noise…

Will continue after work!

Alrighty…well, got myself back to where I was. The noise stuff is working pretty good, the only problem is I have seams between each patch. I tried a combine function, but that didn’t work. Evidently I had to many pieces to combine.

Anyone have any ideas/insights? I’m sort of approaching the limit of what I can figure out and complete, so help would definitely be appreciated!

Also…Anyone have any idea how to get a seamless set of textures generated? Right now I just generate six separate and independent textures that are in no way contiguous with one another (which you can see). Each face though works out well. I need to decouple the size of the texture maps from the size of each face, but for now this is working as a good starting point for understanding what’s going on…and I can easily enough adapt the existing code for different size height maps once I get the seamless bits and joined meshes working.

Here’s the updated code!

``````using UnityEngine;
using System.Collections;
using System.Collections.Generic;

using LibNoise;
using LibNoise.Generator;
using LibNoise.Operator;

public class NewTestPlanet : MonoBehaviour
{

//The number of Subdivions per Face
public int subDivisions = 2;
//The number of Vertices per Subdivision
public int vertsPerSubDivision = 4;

//Texture Data
public float xOrg = 1.0f;
public float yOrg = 1.0f;
public float scale = 10.0f;

public enum cubeIndex { TOP, BOTTOM, LEFT, RIGHT, FRONT, BACK }

public Texture2D[] textures = new Texture2D[6];

public List<Mesh> meshList;

void Start()
{

CreateSubdivisionSurfaces();
}

//Responsible for calling CreateSurface. Passes the following arguments to CreateSurface():
//  x-index (i)
//  z-index (j)
//  cube face (index)
void CreateSubdivisionSurfaces()
{
//For each face and for each Sub Division Surface, Create a new Surface
for (int index = 0; index < 6; index++)
{
for (int i = 0; i < subDivisions; i++)
{
for (int j = 0; j < subDivisions; j++)
{
GenerateSurfaceTextures(index);
GenerateSurfaceCoordinates(i, j, (cubeIndex)index);
}
}
}
//Now that everything has been created, combine it...
//CombineSurfaceMesh();
}

//Creates the surfaces called by CreateSubdivisionSurfaces(). Creates a GameObject
//assigns a MeshRenderer and a MeshCollider to the created Game Object.
void GenerateSurfaceCoordinates(int xIndex, int zIndex, cubeIndex facing)
{
//Appropriately size (and create) the Vertex Array
Vector3[] vertices = new Vector3[vertsPerSubDivision * vertsPerSubDivision];
//Appropriately size (and create) the UV Array
Vector2[] uv  = new Vector2[vertices.Length];

//Calculate the increment to keep the vertices constrained to a 1x1 cube.
float increment = (1.0f / ((float)vertsPerSubDivision - 1)) / (float)subDivisions;
float uvIncrement = (1.0f / ((float)vertsPerSubDivision - 1)) / (float)subDivisions;
//A float to hold returned noise for a given pixel
//Texture size is equal to the number of sub divisions x the vertices per subdivision
int textureSize = subDivisions * vertsPerSubDivision;
//Create the pixel color array
float[] noise = new float[textureSize * textureSize];

for (int i = 0, index = 0; i < vertsPerSubDivision; i++)
{
for (int j = 0; j < vertsPerSubDivision; j++, index++)
{
//Vertex Coordinates
float xPos = (float)j * increment - 0.5f + ((float)xIndex / (float)subDivisions);
float yPos = 0.5f;
float zPos = (float)i * increment - 0.5f + ((float)zIndex / (float)subDivisions);
//UV Coordinates
float xUV = (float)j * uvIncrement + ((float)xIndex / (float)subDivisions);
float zUV = (float)i * uvIncrement + ((float)zIndex / (float)subDivisions);

switch(facing)
{
case cubeIndex.TOP:
//Assign Vertex Coordinates
vertices[index] = new Vector3(xPos, yPos, zPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
//Store noise values
noise[index] = textures[(int)facing].GetPixel(j + (xIndex * vertsPerSubDivision), i + (zIndex * vertsPerSubDivision)).grayscale;
break;

case cubeIndex.BOTTOM:
//Assign Vertex Coordinates
vertices[index] = new Vector3(xPos, -yPos, -zPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
//Store noise values
noise[index] = textures[(int)facing].GetPixel(j + (xIndex * vertsPerSubDivision), i + (zIndex * vertsPerSubDivision)).grayscale;
break;

case cubeIndex.LEFT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-yPos, zPos, -xPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
//Store noise values
noise[index] = textures[(int)facing].GetPixel(j + (xIndex * vertsPerSubDivision), i + (zIndex * vertsPerSubDivision)).grayscale;
break;

case cubeIndex.RIGHT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(yPos, zPos, xPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
//Store noise values
noise[index] = textures[(int)facing].GetPixel(j + (xIndex * vertsPerSubDivision), i + (zIndex * vertsPerSubDivision)).grayscale;
break;

case cubeIndex.FRONT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-xPos, zPos, yPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
//Store noise values
noise[index] = textures[(int)facing].GetPixel(j + (xIndex * vertsPerSubDivision), i + (zIndex * vertsPerSubDivision)).grayscale;
break;

case cubeIndex.BACK:
//Assign Vertex Coordinates
vertices[index] = new Vector3(xPos, zPos, -yPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
//Store noise values
noise[index] = textures[(int)facing].GetPixel(j + (xIndex * vertsPerSubDivision), i + (zIndex * vertsPerSubDivision)).grayscale;
break;
}

//Spherize the Vertices
vertices[index] = vertices[index].normalized;
//Set the newly created sphere equal to the Radius
}
}
//Create the Surface Object
CreateSurfaceObject(vertices, uv, facing, xIndex, zIndex);
}

//Creates a new GameObject to which a MeshRenderer, MeshCollider and MeshFilter are attached.
//This is then passed to CreateMeshSurface where the actual Mesh is created.
void CreateSurfaceObject(Vector3[] vertexArray, Vector2[] uvArray, cubeIndex facing, int xIndex, int zIndex)
{
//Create a new GameObject
GameObject surface = new GameObject(facing + "Surface(" + xIndex + ", " + zIndex + ")");
surface.tag     = gameObject.tag;
surface.layer   = gameObject.layer;
surface.transform.parent    = transform;
surface.transform.position  = transform.position;

//Initialize the Renderer
surface.GetComponent<Renderer>().enabled = true;

//Assign the generated textures to the cube sphere
surface.GetComponent<Renderer>().material.mainTexture = textures[(int)facing];

//Create the Mesh
CreateSurfaceMesh(surface, vertexArray, uvArray, facing);
}

void CreateSurfaceMesh(GameObject surface, Vector3[] vertexArray, Vector2[] uvArray, cubeIndex facing)
{
//Create and size the Vertex Buffer
int vertBufferSize = vertsPerSubDivision - 1;
int[] vertexBuffer = new int[(vertBufferSize * vertBufferSize) * 6];
//Create a new Mesh object
Mesh surfaceMesh;
//Create a new Mesh using the Objects MeshFilter
surfaceMesh = surface.GetComponent<MeshFilter>().sharedMesh = new Mesh();
surfaceMesh.name = surface.name;

//Step through the Vertex Buffer
for (int triIndex = 0, vertIndex = 0, i = 0; i < vertBufferSize; i++, vertIndex++)
{
for (int j = 0; j < vertBufferSize; j++, triIndex += 6, vertIndex++)
{
//  1
//  | \
//  |  \
//  |   \
//  0----2
vertexBuffer[triIndex]      = vertIndex;
vertexBuffer[triIndex + 1]  = vertIndex + vertsPerSubDivision;
vertexBuffer[triIndex + 2]  = vertIndex + 1;
//  1----3
//    \  |
//     \ |
//      \|
//       2
vertexBuffer[triIndex + 3]  = vertIndex + vertsPerSubDivision;
vertexBuffer[triIndex + 4]  = vertIndex + vertsPerSubDivision + 1;
vertexBuffer[triIndex + 5]  = vertIndex + 1;
}
}
//Assign the Vertex Array from Generate Surface Coordinates to the Mesh Vertex Array
surfaceMesh.vertices = vertexArray;
//Assign the UV Coordinates
surfaceMesh.uv = uvArray;
//Assign the Vertex Buffer
surfaceMesh.triangles = vertexBuffer;
//Recalculate the Normals
surfaceMesh.RecalculateNormals();
//After the Mesh has been created, pass it back to the MeshCollider
surface.GetComponent<MeshCollider>().sharedMesh = surfaceMesh;

}

//This is a test...it doesn't work...well, it does...but there are to many surface objects for it to work...
public void CombineSurfaceMesh()
{
//Acquire ALL the mesh filters created
MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
//Create a new combine instance array equal to the size of the number of mesh filters in the object
CombineInstance[] combine = new CombineInstance[meshFilters.Length];

for (int i = 0; i < meshFilters.Length; i++)
{
combine[i].mesh = meshFilters[i].sharedMesh;
combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
meshFilters[i].gameObject.SetActive(false);
}

transform.GetComponent<MeshFilter>().mesh = new Mesh();
transform.GetComponent<MeshFilter>().mesh.CombineMeshes(combine);
transform.gameObject.SetActive(true);
}

public void GenerateSurfaceTextures(int index)
{
//Texture size is equal to the number of sub divisions x the vertices per subdivision
int textureSize = subDivisions * vertsPerSubDivision;
//Create the pixel color array
Color[] pixelColor = new Color[textureSize * textureSize];

for (int i = 0; i < 6; i++)
{
textures[i] = new Texture2D(textureSize, textureSize);
textures[i].wrapMode = TextureWrapMode.Clamp;

float y = 0.0f;
while (y < textureSize)
{
float x = 0.0f;
while (x < textureSize)
{
float xCoord = xOrg + x / textureSize * scale;
float yCoord = xOrg + y / textureSize * scale;
float sample = Mathf.PerlinNoise(xCoord, yCoord);
pixelColor[(int)y * textureSize + (int)x] = new Color(sample, sample, sample);
x++;
}
y++;
}
textures[i].SetPixels(pixelColor);
textures[i].Apply();
}
}
}
``````

It is amazing the work you have put into this!

Something I noticed is when you call GenerateSurfaceTextures(index) in the loop, it recreates the whole array each time. I know its a work-in-progress, but for now maybe use flat textures, generated before the meshes so they can be applied to the renderers.

RecalculateNormals doesn’t do a perfect job, the seams can be noticed when you use a flat colour. For a perfect sphere just using the vertex normalized is fine, but for a noisy sphere … huge task. CatLikeCoding covers this in this post (scroll down to Calculating Normals, about 2/5 down the page).
Quote

To calculate normals ourselves, we have to loop through all vertices and figure out the normal of each one. Because our mesh is a grid, most vertices have four neighbors. If you connect opposite neighbors, you get two lines that form a cross. Those two lines define a plane, which we can consider an approximation of the tangent plane of the slope at the center vertex. If we take the cross product of those two vectors and normalize it, we end up with the normal of that slope.

As we consider the two lines lines tangent to the slope at our vertex, then if we scale them so that their X or Z component is 1, then their Y component is the proper rate of change in that direction. Let’s go one step at a time, and misuse our normals to show the rate of change – the derivative – of the X direction only. For that reason we won’t assign our normals to the mesh just yet

Which also brings up mesh tangents if you want to create bumpmaps as well as textures.

With noise, 3D Simplex noise is the way to go. You can get a value directly using the Vector3 vertex position, even do some calculations to get a fractal noise rather than just basic waves.

Overall I think creating procedural textures is a mammoth task. Just like adding noise to the vertices, you will have to know where every pixel is located in 3D space to evaluate 3D noise and apply it to the texture. Have you considered using vertex colours instead of textures? This could be calculated and applied while you are doing the verts and uvs (and normals eventually).

Here are some scripts from playing around with your amazing work.

This one creates flat grey textures, then uses 3D noise to offset the heights. I have attached the Noise script I used :
PlanetTest with flat textures and 3D Noise

``````using UnityEngine;
using System.Collections;

public class PlanetTest : MonoBehaviour
{
public float noiseScale = 2.8f;
public float noiseHeight = 0.5f;

//   -------------------------------------------------------  Persistent Functions

void Start()
{
CreateSubdivisionSurfaces();
}

//   -------------------------------------------------------  Planet Generation Functions

//The number of Subdivions per Face
public int subDivisions = 4;
//The number of Vertices per Subdivision
public int vertsPerSubDivision = 16;

//Texture Data
public float xOrg = 1.0f;
public float yOrg = 1.0f;
public float scale = 10.0f;

public enum cubeIndex { TOP, BOTTOM, LEFT, RIGHT, FRONT, BACK }

public Texture2D[] textures;

//Responsible for calling CreateSurface. Passes the following arguments to CreateSurface():
//  x-index (i)
//  z-index (j)
//  cube face (index)
void CreateSubdivisionSurfaces()
{
// ** FOR TESTING **
GenerateSurfaceTextures();

//For each face and for each Sub Division Surface, Create a new Surface
for (int index = 0; index < 6; index++)
{
for (int i = 0; i < subDivisions; i++)
{
for (int j = 0; j < subDivisions; j++)
{
//GenerateSurfaceTextures(index);
GenerateSurfaceCoordinates(i, j, (cubeIndex)index);
}
}
}
}

//Creates the surfaces called by CreateSubdivisionSurfaces(). Creates a GameObject
//assigns a MeshRenderer and a MeshCollider to the created Game Object.
void GenerateSurfaceCoordinates(int xIndex, int zIndex, cubeIndex facing)
{
//Appropriately size (and create) the Vertex Array
Vector3[] vertices = new Vector3[vertsPerSubDivision * vertsPerSubDivision];
//Appropriately size (and create) the UV Array
Vector2[] uv  = new Vector2[vertices.Length];

//Calculate the increment to keep the vertices constrained to a 1x1 cube.
float increment = (1.0f / ((float)vertsPerSubDivision - 1)) / (float)subDivisions;
float uvIncrement = (1.0f / ((float)vertsPerSubDivision - 1)) / (float)subDivisions;

for (int i = 0, index = 0; i < vertsPerSubDivision; i++)
{
for (int j = 0; j < vertsPerSubDivision; j++, index++)
{
//Vertex Coordinates
float xPos = (float)j * increment - 0.5f + ((float)xIndex / (float)subDivisions);
float yPos = 0.5f;
float zPos = (float)i * increment - 0.5f + ((float)zIndex / (float)subDivisions);
//UV Coordinates
float xUV = (float)j * uvIncrement + ((float)xIndex / (float)subDivisions);
float zUV = (float)i * uvIncrement + ((float)zIndex / (float)subDivisions);

switch(facing)
{
case cubeIndex.TOP:
//Assign Vertex Coordinates
vertices[index] = new Vector3(xPos, yPos, zPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.BOTTOM:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-xPos, -yPos, zPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.LEFT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-yPos, zPos, -xPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.RIGHT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(yPos, zPos, xPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.FRONT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-xPos, zPos, yPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.BACK:
//Assign Vertex Coordinates
vertices[index] = new Vector3(xPos, zPos, -yPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;
}

//Spherize the Vertices
vertices[index] = vertices[index].normalized;

// get noise
float noise = Noise.Noise.GetNoise( vertices[index].x * noiseScale, vertices[index].y * noiseScale, vertices[index].z * noiseScale );
noise *= noiseHeight;

//Set the newly created sphere equal to the Radius
}
}
//Create the Surface Object
CreateSurfaceObject(vertices, uv, facing, xIndex, zIndex);
}

//Creates a new GameObject to which a MeshRenderer, MeshCollider and MeshFilter are attached.
//This is then passed to CreateMeshSurface where the actual Mesh is created.
void CreateSurfaceObject(Vector3[] vertexArray, Vector2[] uvArray, cubeIndex facing, int xIndex, int zIndex)
{
//Create a new GameObject
GameObject surface = new GameObject(facing + "Surface(" + xIndex + ", " + zIndex + ")");
surface.tag  = gameObject.tag;
surface.layer  = gameObject.layer;
surface.transform.parent  = transform;
surface.transform.position  = transform.position;

//Initialize the Renderer
surface.GetComponent<Renderer>().enabled = true;

//Assign the generated textures to the cube sphere
surface.GetComponent<Renderer>().material.mainTexture = textures[(int)facing];

//Create the Mesh
CreateSurfaceMesh(surface, vertexArray, uvArray, facing);
}

void CreateSurfaceMesh(GameObject surface, Vector3[] vertexArray, Vector2[] uvArray, cubeIndex facing)
{
//Create and size the Vertex Buffer
int vertBufferSize = vertsPerSubDivision - 1;
int[] vertexBuffer = new int[(vertBufferSize * vertBufferSize) * 6];
//Create a new Mesh object
Mesh surfaceMesh;
//Create a new Mesh using the Objects MeshFilter
surfaceMesh = surface.GetComponent<MeshFilter>().sharedMesh = new Mesh();
surfaceMesh.name = surface.name;

//Step through the Vertex Buffer
for (int triIndex = 0, vertIndex = 0, i = 0; i < vertBufferSize; i++, vertIndex++)
{
for (int j = 0; j < vertBufferSize; j++, triIndex += 6, vertIndex++)
{
//  1
//  | \
//  |  \
//  |  \
//  0----2
vertexBuffer[triIndex]  = vertIndex;
vertexBuffer[triIndex + 1]  = vertIndex + vertsPerSubDivision;
vertexBuffer[triIndex + 2]  = vertIndex + 1;
//  1----3
//  \  |
//  \ |
//  \|
//  2
vertexBuffer[triIndex + 3]  = vertIndex + vertsPerSubDivision;
vertexBuffer[triIndex + 4]  = vertIndex + vertsPerSubDivision + 1;
vertexBuffer[triIndex + 5]  = vertIndex + 1;
}
}
//Assign the Vertex Array from Generate Surface Coordinates to the Mesh Vertex Array
surfaceMesh.vertices = vertexArray;
//Assign the UV Coordinates
surfaceMesh.uv = uvArray;
//Assign the Vertex Buffer
surfaceMesh.triangles = vertexBuffer;
//Recalculate the Normals
surfaceMesh.RecalculateNormals();
//After the Mesh has been created, pass it back to the MeshCollider
surface.GetComponent<MeshCollider>().sharedMesh = surfaceMesh;
}

public void GenerateSurfaceTextures( int index )
{
//Texture size is equal to the number of sub divisions x the vertices per subdivision
int textureSize = subDivisions * vertsPerSubDivision;
//Create the texture and pixel color arrays
textures = new Texture2D[6];
Color[] pixelColor = new Color[textureSize * textureSize];

for (int i = 0; i < 6; i++)
{
textures[i] = new Texture2D(textureSize, textureSize);

float y = 0.0f;
while (y < textureSize)
{
float x = 0.0f;
while (x < textureSize)
{
float xCoord = xOrg + x / textureSize * scale;
float yCoord = xOrg + y / textureSize * scale;
float sample = Mathf.PerlinNoise(xCoord, yCoord);
pixelColor[(int)y * textureSize + (int)x] = new Color(sample, sample, sample);
x++;
}
y++;
}
textures[i].SetPixels(pixelColor);
textures[i].Apply();
}
}

// ** FOR TESTING **
public void GenerateSurfaceTextures()
{
//Texture size is equal to the number of sub divisions x the vertices per subdivision
int textureSize = subDivisions * vertsPerSubDivision;
//Create the texture and pixel color arrays
textures = new Texture2D[6];
Color[] pixelColor = new Color[textureSize * textureSize];

for (int i = 0; i < 6; i++)
{
textures[i] = new Texture2D(textureSize, textureSize);

float y = 0.0f;
while (y < textureSize)
{
float x = 0.0f;
while (x < textureSize)
{
pixelColor[(int)y * textureSize + (int)x] = Color.grey;
x++;
}
y++;
}
textures[i].SetPixels(pixelColor);
textures[i].Apply();
}
}
}
``````

This script uses Vertex Colours instead of textures. Create a material, use Custom/VertexColored shader, then drop in the inspector. I have attached the Shader I used :
PlanetTestVertexColour with vertex colours and 3D Noise

``````using UnityEngine;
using System.Collections;

public class PlanetTestVertexColour : MonoBehaviour
{
public float noiseScale = 2.8f;
public float noiseHeight = 0.5f;

public Material vertexColoured;

//   -------------------------------------------------------  Persistent Functions

void Start()
{
CreateSubdivisionSurfaces();
}

//   -------------------------------------------------------  Planet Generation Functions

//The number of Subdivions per Face
public int subDivisions = 4;
//The number of Vertices per Subdivision
public int vertsPerSubDivision = 16;

//Texture Data
public float xOrg = 1.0f;
public float yOrg = 1.0f;
public float scale = 10.0f;

public enum cubeIndex { TOP, BOTTOM, LEFT, RIGHT, FRONT, BACK }

//Responsible for calling CreateSurface. Passes the following arguments to CreateSurface():
//  x-index (i)
//  z-index (j)
//  cube face (index)
void CreateSubdivisionSurfaces()
{
//For each face and for each Sub Division Surface, Create a new Surface
for (int index = 0; index < 6; index++)
{
for (int i = 0; i < subDivisions; i++)
{
for (int j = 0; j < subDivisions; j++)
{
//GenerateSurfaceTextures(index);
GenerateSurfaceCoordinates(i, j, (cubeIndex)index);
}
}
}
}

//Creates the surfaces called by CreateSubdivisionSurfaces(). Creates a GameObject
//assigns a MeshRenderer and a MeshCollider to the created Game Object.
void GenerateSurfaceCoordinates(int xIndex, int zIndex, cubeIndex facing)
{
//Appropriately size (and create) the Vertex Array
Vector3[] vertices = new Vector3[vertsPerSubDivision * vertsPerSubDivision];
//Appropriately size (and create) the UV Array
Vector2[] uv  = new Vector2[vertices.Length];
//Appropriately size (and create) the Vertex Colour Array
Color[] colours = new Color[vertices.Length];

//Calculate the increment to keep the vertices constrained to a 1x1 cube.
float increment = (1.0f / ((float)vertsPerSubDivision - 1)) / (float)subDivisions;
float uvIncrement = (1.0f / ((float)vertsPerSubDivision - 1)) / (float)subDivisions;

for (int i = 0, index = 0; i < vertsPerSubDivision; i++)
{
for (int j = 0; j < vertsPerSubDivision; j++, index++)
{
//Vertex Coordinates
float xPos = (float)j * increment - 0.5f + ((float)xIndex / (float)subDivisions);
float yPos = 0.5f;
float zPos = (float)i * increment - 0.5f + ((float)zIndex / (float)subDivisions);
//UV Coordinates
float xUV = (float)j * uvIncrement + ((float)xIndex / (float)subDivisions);
float zUV = (float)i * uvIncrement + ((float)zIndex / (float)subDivisions);

switch(facing)
{
case cubeIndex.TOP:
//Assign Vertex Coordinates
vertices[index] = new Vector3(xPos, yPos, zPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.BOTTOM:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-xPos, -yPos, zPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.LEFT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-yPos, zPos, -xPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.RIGHT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(yPos, zPos, xPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.FRONT:
//Assign Vertex Coordinates
vertices[index] = new Vector3(-xPos, zPos, yPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;

case cubeIndex.BACK:
//Assign Vertex Coordinates
vertices[index] = new Vector3(xPos, zPos, -yPos);
//Assign UV Coordinates
uv[index] = new Vector2(xUV, zUV);
break;
}

//Spherize the Vertices
vertices[index] = vertices[index].normalized;

// get Noise
float noise = Noise.Noise.GetNoise( vertices[index].x * noiseScale, vertices[index].y * noiseScale, vertices[index].z * noiseScale );

// get Vertex Colour
colours[index] = planetColours.Evaluate( noise );

//Set the newly created sphere equal to the Radius
vertices[index] *= planetRadius + ( noise * noiseHeight );
}
}
//Create the Surface Object
CreateSurfaceObject(vertices, uv, colours, facing, xIndex, zIndex);
}

//Creates a new GameObject to which a MeshRenderer, MeshCollider and MeshFilter are attached.
//This is then passed to CreateMeshSurface where the actual Mesh is created.
void CreateSurfaceObject(Vector3[] vertexArray, Vector2[] uvArray, Color[] colourArray, cubeIndex facing, int xIndex, int zIndex)
{
//Create a new GameObject
GameObject surface = new GameObject(facing + "Surface(" + xIndex + ", " + zIndex + ")");
surface.tag  = gameObject.tag;
surface.layer  = gameObject.layer;
surface.transform.parent  = transform;
surface.transform.position  = transform.position;

//Initialize the Renderer
surface.GetComponent<Renderer>().enabled = true;

//Assign the generated textures to the cube sphere
surface.GetComponent<Renderer>().material = vertexColoured;

//Create the Mesh
CreateSurfaceMesh(surface, vertexArray, uvArray, colourArray, facing);
}

void CreateSurfaceMesh(GameObject surface, Vector3[] vertexArray, Vector2[] uvArray, Color[] colourArray, cubeIndex facing)
{
//Create and size the Vertex Buffer
int vertBufferSize = vertsPerSubDivision - 1;
int[] vertexBuffer = new int[(vertBufferSize * vertBufferSize) * 6];
//Create a new Mesh object
Mesh surfaceMesh;
//Create a new Mesh using the Objects MeshFilter
surfaceMesh = surface.GetComponent<MeshFilter>().sharedMesh = new Mesh();
surfaceMesh.name = surface.name;

//Step through the Vertex Buffer
for (int triIndex = 0, vertIndex = 0, i = 0; i < vertBufferSize; i++, vertIndex++)
{
for (int j = 0; j < vertBufferSize; j++, triIndex += 6, vertIndex++)
{
//  1
//  | \
//  |  \
//  |  \
//  0----2
vertexBuffer[triIndex]  = vertIndex;
vertexBuffer[triIndex + 1]  = vertIndex + vertsPerSubDivision;
vertexBuffer[triIndex + 2]  = vertIndex + 1;
//  1----3
//  \  |
//  \ |
//  \|
//  2
vertexBuffer[triIndex + 3]  = vertIndex + vertsPerSubDivision;
vertexBuffer[triIndex + 4]  = vertIndex + vertsPerSubDivision + 1;
vertexBuffer[triIndex + 5]  = vertIndex + 1;
}
}
//Assign the Vertex Array from Generate Surface Coordinates to the Mesh Vertex Array
surfaceMesh.vertices = vertexArray;
//Assign the UV Coordinates
surfaceMesh.uv = uvArray;
//Assign the Vertex Buffer
surfaceMesh.triangles = vertexBuffer;
// Assign the Vertx Colours
surfaceMesh.colors = colourArray;
//Recalculate the Normals
surfaceMesh.RecalculateNormals();
//After the Mesh has been created, pass it back to the MeshCollider
surface.GetComponent<MeshCollider>().sharedMesh = surfaceMesh;
}

//   -------------------------------------------------------  Planet Colour Gradient Functions

{
// using colour scale from LibNoise example : http://libnoise.sourceforge.net/tutorials/tutorial3.html

gck[0].color = CalculateGradientColour(  0,  0, 128 );
gck[1].color = CalculateGradientColour(  0,  0, 255 );
gck[2].color = CalculateGradientColour(  0, 128, 255 );
gck[3].color = CalculateGradientColour(  240, 240,  64 );
gck[4].color = CalculateGradientColour(  32, 160,  0 );
gck[5].color = CalculateGradientColour( 224, 224,  0 );
gck[6].color = CalculateGradientColour( 128, 128, 128 );
gck[7].color = CalculateGradientColour( 255, 255, 255 );

gak[0].alpha = 1f;
gak[0].time = 0f;
gak[1].alpha = 1f;
gak[1].time = 1f;

planetColours.SetKeys( gck, gak );
}

Color CalculateGradientColour( int r, int g, int b )
{
return new Color( (float)r / 255f, (float)g / 255f, (float)b / 255f );
}

{
return (float)( ( t + 1 ) * 0.5 );
}
}
``````

You can remove CreateColourGradient() from the start function and assign your own gradient colours in the inspector

Again, great work, I look forward to seeing where you take this. All the Best.

Edit : not trying to persuade you to use vertex colours or anything, but I was playing around with fractal after posting the above. Modify the PlanetTestVertexColour script like this :
PlanetTestVertexColour with fractal noise

``````replace line 138 with :
float noise = GetNoise( vertices[index] ); // Noise.Noise.GetNoise( vertices[index].x * noiseScale, vertices[index].y * noiseScale, vertices[index].z * noiseScale );

//   -------------------------------------------------------  Noise Functions

public bool useBasicNoise = false;

public int octaves = 8;
public float amp = 1f;

float GetNoise( Vector3 vertex )
{
// basic
if ( useBasicNoise )
{
return Noise.Noise.GetNoise( vertex.x * noiseScale, vertex.y * noiseScale, vertex.z * noiseScale );
}

// fractal
float noise = 0f;
float gain = 1f;
float factor = 0f;

for ( int i = 0; i < octaves; i++ )
{
factor += 1f / gain;

noise += Noise.Noise.GetNoise( vertex.x * noiseScale * gain, vertex.y * noiseScale * gain, vertex.z * noiseScale * gain ) * ( amp / gain );

gain *= 2f;
}

noise /= factor;

return noise;
}
``````

I used 8 subdivisions and 16 verts per subdivision in the screenshots.
The issue with the normals is still there, something you have to deal with using the texture or vertex colour method.

Edit 2 :
It just occurred to me that creating the textures would actually be the same method as for calculating the vertices! Just by using 1 for subdivision, and the pixels (eg 512) for verts per subdivision
Also take a look at this CubeSphere tutorial by CatLikeCoding. It has a great method for spacing the vertices more evenly.

2634676–185252–Noise.cs (9.76 KB)

3 Likes

Wow! You’re the man Alucardj! So…here’s what I learned from what you did there, and please correct me if I’m wrong on my understanding:

Rather than trying to find where a vertex falls on a flat plane it’s easier to use 3D Noise. This 3D noise in my mind conceptually is like standing inside of a cloud. All around me is a noise, or fog or some other obstruction to vision. Some of that obscuration happens to be tangent with my sphere, my goal is to use the vertices of my sphere to find out how high that particular point in space is. So the noise is…just hangin’ out in the world and we say 'at this vertex location, how ‘high’ is that noise?

Does that make sense? I’ve got a bunch of clouds hanging out in the world and I’m just comparing how much of a cloud is at my vertex location in the 3D space. It could be 0, it could be 255, or some number there in between. After the sampling takes place, and Can then say 'hey, at this point on the texture map the brightness is 122, the vertex beside and around is uniformly (unlikely, but for the sake of argument) 120, the next vertex ring is 115. And that generates the height map based on the difference in altitude between the vertex post noise application and the vertex pre noise application.

The shader is awesome too! I didn’t even think to use a shader (mainly because I have zero clue how to right shader code…my one foray ended in utter disaster with wailing and gnashing of teeth and birds falling out of the sky ;))

Cool! Thank you. I’ll get back here once I’ve dug a bit more into what you’ve done and have a firmer grasp on what’s going on!

Your cloud analogy is spot on. At this vertex position, how ‘dense’ is the cloud. That’s 3D noise.

I made two scripts to help visualize this. (I really like procedural generation, and playing with noise!) :
1/ SimplexNoiseExample.cs : create a sphere, attach the vertex coloured material and the script, then hit play.
2/ SimplexNoiseCubes.cs : create an empty gameObject, attach the script, then hit play.

While running, play around toggling the axis booleans, and changing the speed. The cube example is much more fun, can really visualize moving through the cloud of noise.

I have to thank you too! First for the interesting question and your efforts. And I’ve always wanted to make procedural nebula skyboxes, and you’ve helped me think about how I can do that now (something you may be interested in as well).

Oh, and about this link regarding spacing the vertices more evenly, I realized that your uvs are actually evenly spaced, so at the moment they are stretched and compressed between the vertices of your meshes.That’s what makes uv mapping a sphere so much fun… But if you implement the more evenly spaced vertices, that will make the distortion a little less noticeable. I’ll play around with it myself to see what I discover. But I already wrote in the spaced vertices if you want :
more evenly spaced vertices

``````// replace :
vertices[index] = vertices[index].normalized;
// with :
vertices[index] = SmoothedVertex( vertices[index] );

// then add this function :
Vector3 SmoothedVertex( Vector3 vertex )
{
Vector3 v = vertex * 2f;
float x2 = v.x * v.x;
float y2 = v.y * v.y;
float z2 = v.z * v.z;

Vector3 s;
s.x = v.x * Mathf.Sqrt(1f - y2 / 2f - z2 / 2f + y2 * z2 / 3f);
s.y = v.y * Mathf.Sqrt(1f - x2 / 2f - z2 / 2f + x2 * z2 / 3f);
s.z = v.z * Mathf.Sqrt(1f - x2 / 2f - y2 / 2f + x2 * y2 / 3f);

return s;
}
``````

I will stop making suggestions now, give you a chance to explore for yourself. :S

EDIT : a little motivational teaser for you. Textures are definitely the way to go.

Smoothing the vertices fixes the UV problem anyway.
Generate each face texture the same way as for vertices. Use 1 subdivision, pixels for verts per subdivision.

2635828–185378–SimplexNoiseExample.cs (1.64 KB)
2635828–185379–SimplexNoiseCubes.cs (2.52 KB)

2 Likes

Awesome! Great stuff! I’ve been busy working on wrapping my head around this and also implementing the next big piece of this puzzle:

Procedural Solar System Generation

I’ve got a bunch of articles I’ve bought and books too that I’ve been using here’s a link to the articles in my DropBox:
https://www.dropbox.com/sh/ock9z0xqy6nswrd/AADTh3bApQZzxd4V0aAuiZlUa?dl=0 (let me know if you have issues snagging it)

I’ve been trying to more or less convert (with little success) the C code for a program called Accrete (http://znark.com/create/accrete.html) and also (http://www.eldacur.com/~brons/NerdCorner/StarGen/StarGen.html). The problem is…C# has some quirks to it that make the conversion…problematic. One of the biggest problems I’m running into surrounds Structs and Pointers. Pointers it turns out are unsafe…as I understand it, C# doesn’t let you go into the memory realm because of it’s garbage collection functions, so if you want to go directly into memory, you have to declare that code as ‘unsafe’ and do some special things in unity to make it work. So what I did was essentially convert everything to classes. Here’s an example of one of the classes I’ve converted:

``````using UnityEngine;
using System.Collections;

public class Dust : MonoBehaviour
{
private float innerEdge;
private float outerEdge;
private bool dustPresent;
private bool gasPresent;

private Dust nextDustBand;

//Helper functions
public float InnerEdge
{
get { return innerEdge; }
set { innerEdge = value; }
}

public float OuterEdge
{
get { return outerEdge; }
set { outerEdge = value; }
}

public bool DustPresent
{
get { return dustPresent; }
set { dustPresent = value; }
}

public bool GasPresent
{
get { return gasPresent; }
set { gasPresent = value; }
}

public Dust NextDustBand
{
get { return nextDustBand; }
set { nextDustBand = value; }
}
}
``````

and Here’s the Original C version:

``````typedef struct dust_record    *dust_pointer;

typedef struct dust_record {
long double inner_edge;
long double outer_edge;
int         dust_present;
int         gas_present;
dust_pointer next_band;
} dust;
``````

Kind of a problem, considering the Pointer there. Where I run into an issue, if I understand the difference between the pass by reference and pass by value differences between a class and struct, is here:

``````    Dust dustStart;
Planet planetStart;

//1.
void SetInitialConditions(float innerDustLimit, float outerDustLimit)
{
dustStart = new Dust();
planetStart = new Planet();

dustStart.InnerEdge = innerDustLimit;
dustStart.OuterEdge = outerDustLimit;
dustStart.DustPresent = true;
dustStart.GasPresent = true;
dustStart.NextDustBand = null;

dustLeft = true;
dustCloudEccentricity = 0.2f;

if (dustStart == null)
{
Debug.Log("dustStart is NULL");
}
}
``````

Duststart is ALWAYS null…which factors into other code down the line…Even though I create a new Dust object, and initilialize all the relevant bits, it returns NULL…which is bad. Because when I go to check later down the line if something is null:

`````` //5. Calculate the Dust Available
bool IsDustAvailable(float innerEffectLimit, float outerEffectLimit)
{
Dust currentDustBand;
bool dustHere;

currentDustBand = dustStart;
Debug.Log("Current Dust Band: " + currentDustBand);

//So long as the current dust band isn't null AND the current dust bands outer radius is still less than the inner effect limit, then set the current dust
//band to the next dust band
while ((currentDustBand != null) && (currentDustBand.OuterEdge < innerEffectLimit))
{
Debug.Log("1");
currentDustBand = currentDustBand.NextDustBand;
}

//If the current dust band is null, then there's no more dust...otherwise, there's still dust
if (currentDustBand == null)
{
Debug.Log("currentDustBand == null");
dustHere = false;
}
else
{
Debug.Log("currentDustBand != null");
dustHere = currentDustBand.DustPresent;
}

while ((currentDustBand != null) && (currentDustBand.InnerEdge < outerEffectLimit))
{
Debug.Log("4");
dustHere = dustHere || currentDustBand.DustPresent;
currentDustBand = currentDustBand.NextDustBand;
}

return dustHere;
}
``````

It always returns currentDustBand as NULL…even though I’m passing an initialized object…

So…anyone have any ideas here? I’m slowly reading through everything I can on the subject…some of it makes sense, some of it doesn’t. Any insights into what is going on would be awesome! I definitely want to use the Accrete bits as the core of my Planet Generation code, it’s just a matter of getting it to work with C#. Which I know I’ll eventually figure out.

Oh!

``````using UnityEngine;
using System.Collections;

public class GravityAttractor : MonoBehaviour
{
public float gravity = -9.806f;

public float gravityPerSecond;

public void Attract(Transform body)
{
Vector3 targetDirection = (body.position - transform.position).normalized;

Vector3 bodyUp = body.up;

body.rotation = Quaternion.FromToRotation(bodyUp, targetDirection) * body.rotation;
}
}

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Rigidbody))]

public class GravityBody : MonoBehaviour
{
GravityAttractor planet;

void Awake()
{
planet = GameObject.Find("Planet").GetComponent<GravityAttractor>();
GetComponent<Rigidbody>().useGravity = false;
GetComponent<Rigidbody>().constraints = RigidbodyConstraints.FreezeRotation;
}

void FixedUpdate()
{
planet.Attract(transform);
}

}
``````

So those two scripts, which I learned from a YouTube video applied to the planet that is generated work like a champ! You can walk around the planet using that script there. So…once the solar system generator is done, and the noise bits are properly working with noise, you’ve got a fully functioning basic framework for procedurally generating a solar system with semi-realistic planets and nice textures.

Anyway, that’s where I’m at right now. I’ll definitely dig deeper into the 3D noise after I’ve got the planet generator solved!

1 Like

Woah, heavy stuff!! Was wondering how you were going, busy I guess! Wasn’t sure if my last edit with image was annoying; am more than happy to give the code used (let me know), just assumed you wanted to wrap your head around it and work it out. I’ve also been looking into calculating mesh tangents and creating bumpmaps at runtime too as you may want them. The normal issue is still bugging me; the only way out is for each mesh to have a reference to its surrounding meshes, so the normals can be manually calculated at the seams. A cheat would be to make every edge normal just the vertex.normalized, but that could create some odd lighting and be just as noticeable.

The struct and pointer thing is waaay beyond my scope, but everything you’ve converted looks ok. With your basic Dust class, you don’t need to inherit Monobehaviour.

In SetInitialConditions(), you assign NextDustBand = null, this is the null you are seeing. the class variable dustStart is initialized fine, and definitely exists.

//5 line 15 you are setting currentDustBand = currentDustBand.NextDustBand; which is making currentDustBand null. If NextDustBand was populated with dust greater than innerEffectLimit, then it would return not null.
example:

``````using UnityEngine;
using System.Collections;

public class Dust
{
private float innerEdge;
private float outerEdge;
private bool dustPresent;
private bool gasPresent;

private Dust nextDustBand; // wow, can declare class variable WITHIN the class!

//Helper functions
public float InnerEdge
{
get { return innerEdge; }
set { innerEdge = value; }
}

public float OuterEdge
{
get { return outerEdge; }
set { outerEdge = value; }
}

public bool DustPresent
{
get { return dustPresent; }
set { dustPresent = value; }
}

public bool GasPresent
{
get { return gasPresent; }
set { gasPresent = value; }
}

public Dust NextDustBand
{
get { return nextDustBand; }
set { nextDustBand = value; }
}
}

// testing struct
public struct DustStruct
{
public float innerEdge;
public float outerEdge;
public bool dustPresent;
public bool gasPresent;

//public DustStruct nextDustBand; // cannot declare struct variable within the struct :(
// error CS0523: Struct member `DustStruct.nextDustBand' of type `DustStruct' causes a cycle in the struct layout
}

public class SolarSystemGenTest : MonoBehaviour
{

//   -------------------------------------------------------  Persistent Functions

void Start()
{
SetInitialConditions( 1f, 2f );

dustStart.NextDustBand = AssignNextDust( 0.8f, 0.9f ); // test function to populate NextDustBand in startDust
dustStart.NextDustBand.NextDustBand = AssignNextDust( 5f, 6f ); // test function to populate NextDustBand in startDust.NextDustBand
// so now there are 3 Dusts : dustStart, dustStart.NextDustBand, and dustStart.NextDustBand.NextDustBand (my head hurts!)

Debug.Log( "" );
Debug.Log( "** TEST 1 - inner is less than **" ); // returns currentDustBand != null
Debug.Log( IsDustAvailable( 3f, 4f ) );
Debug.Log( "" );

Debug.Log( "" );
Debug.Log( "** TEST 2 - inner is greater than **" ); // returns currentDustBand == null
Debug.Log( IsDustAvailable( 8f, 9f ) );
Debug.Log( "" );
}

//   -------------------------------------------------------  Test Functions

Dust AssignNextDust( float innerEdge, float outerEdge )
{
Dust nextDust = new Dust();

nextDust.InnerEdge = innerEdge;
nextDust.OuterEdge = outerEdge;
nextDust.DustPresent = false;
nextDust.GasPresent = false;
nextDust.NextDustBand = null;

return nextDust;
}

//   -------------------------------------------------------  Forum Functions

Dust dustStart;
//Planet planetStart;

//1.
void SetInitialConditions(float innerDustLimit, float outerDustLimit)
{
dustStart = new Dust();
//planetStart = new Planet();

dustStart.InnerEdge = innerDustLimit;
dustStart.OuterEdge = outerDustLimit;
dustStart.DustPresent = true;
dustStart.GasPresent = true;
dustStart.NextDustBand = null;

//dustLeft = true;
//dustCloudEccentricity = 0.2f;

//if (dustStart == null) Debug.Log("dustStart is NULL");
Debug.Log( dustStart == null ? "dustStart is NULL" : "dustStart is populated" );
}

//5. Calculate the Dust Available
bool IsDustAvailable(float innerEffectLimit, float outerEffectLimit)
{
Dust currentDustBand;
bool dustHere;

currentDustBand = dustStart;
Debug.Log("Current Dust Band: " + currentDustBand);

//So long as the current dust band isn't null AND the current dust bands outer radius is still less than the inner effect limit, then set the current dust
//band to the next dust band
while ((currentDustBand != null) && (currentDustBand.OuterEdge < innerEffectLimit))
{
// debug the current dust variables
Debug.Log(" ---- ");
Debug.Log("Dust InnerEdge: "  + currentDustBand.InnerEdge);
Debug.Log("Dust OuterEdge: "  + currentDustBand.OuterEdge);
Debug.Log("Dust DustPresent: "  + currentDustBand.DustPresent);
Debug.Log("Dust GasPresent: "  + currentDustBand.GasPresent);
Debug.Log("Dust NextDustBand: " + ( currentDustBand.NextDustBand == null ? "NULL" : "Populated" ) );
Debug.Log(" ---- ");

Debug.Log("1");
currentDustBand = currentDustBand.NextDustBand;
}

//If the current dust band is null, then there's no more dust...otherwise, there's still dust
if (currentDustBand == null)
{
Debug.Log("currentDustBand == null");
dustHere = false;
}
else
{
Debug.Log("currentDustBand != null");
dustHere = currentDustBand.DustPresent;
}

while ((currentDustBand != null) && (currentDustBand.InnerEdge < outerEffectLimit))
{
Debug.Log("4");
dustHere = dustHere || currentDustBand.DustPresent;
currentDustBand = currentDustBand.NextDustBand;
}

return dustHere;
}
}
``````

If you can decipher all that debug output(!) the while recursion loop
does work when NextDustBand != null and NextDustBand.OuterEdge is < innerEffectLimit ,
but not when NextDustBand != null and NextDustBand.OuterEdge is > innerEffectLimit or NextDustBand == null .

That was just a quick look and test. Hope some of that made sense… I havn’t looked hard at the ACCRETE.C source yet, but it seems all the Dusts have to be declared first, and also assigned to respective nextDust variables. Then you should see some results from the recursion.

1 Like

Alrighty…So this rig is sort of working now. It won’t go beyond injecting 2-3 nuclei…but it’s a start! Here’s the original version in C++ and also my conversion efforts. I need a second set of eyes and definitely someone a bit smarter on the nuances of C# than Myself. I’ve been on a crash course of learning in adapting the existing code over. I suspect it’s a disconnect with the events and coalescence code…I’m getting output for 9 events, which would imply a LOT more nuclei are being injected…I guess it would probably be more helpful to actually have some kind of output to visually verify things too…

I know too that the differences in the number generator are probably causing issues. The test run with a seed of 41 (on an IBM 7044 pc) using the code (which was just a translation from Fortran to C++…and really, Fortran to C to C++), should have reported 17 events, and injected 59 nuclei…The normal ranges for nuclei injection sits around 40 to 500, with the average being less than 150. So there’s definitely something not right in my implementation as it sits right now…I think the biggest issue is that I’m not getting any Gas Giants…whereas I was prior to the code in lines 433 through…481-ish…

So actually, I just commented out the section that I don’t think is working. This immediately led to the formation of gas giants…nuclei injection is still super low…Yup. I need an extra set of eyes.

Anyway, code!

The Original:

``````/*%CC acrete.c -o acrete -lm
*
* This will also work with g++ 1.40.3: g++ acrete.c -o acrete -lm
*
* Dole accretion model for planetary formation.
* Adapted from Wales Larrison's BASIC code.
* References:
*
*  Dole, S.H. "Formation of Planetary Systems by Aggregation:
*        a Computer Simulation" Icarus, vol 13, p 494, 1970
*  Isaacman, R. & Sagan, C. "Computer Simulation of Planetary
*        Accretion Dynamics: Sensitivity to Initial Conditions"
*        Icarus, vol 31, p 510, 1977
*
* Usage:
*  acrete [-seed #] [-dump]
*
*    -seed specifies initial value to random number generator (uses time otherwise)
*    -dump causes a dump of the generated system on stdout
*    Produces a PostScript file "planets.ps"
*
* Jon Leech (leech@cs.unc.edu)
*/
#include <stream.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
// Should include <rand48.h>, but this isn't on the Ultrix distribution
extern "C" {
double  drand48();
long    lrand48();
void    srand48(long);
};

// G++ *still* doesn't have an iostream library, 3 years behind the rest
//  of the world.
#ifdef __GNUG__

// ostream << void *
inline ostream &operator<< (ostream &o, void *p) {
return o << "0x" << hex(long(p));
}

// Manipulators 'endl' and 'flush'
inline ostream &operator<<(ostream &o, ostream &(*manip)(ostream &)) {
return (*manip)(o);
}

inline ostream &endl(ostream &o) { o << '\n'; return o.flush(); }
inline ostream &flush(ostream &o) { return o.flush(); }

typedef long streamoff;

// ofstream w/file name constructor and seekp() method
struct ofstream : public ostream {
ofstream(const char *file) : ostream(new Filebuf(file, io_writeonly, a_create)) { }
};

#endif /*__GNUG__*/

// Simulation parameters
struct DoleParams {
double
A,
B,
Alpha,
Beta,
Eccentricity,
Gamma,
K,
MassSol,
MassSun,
MassNuclei,
W;

DoleParams();
double density(double au);
double gasdensity(double au, double massratio);
double masscritical(double au);
};

// Initialize to defaults. See Sagan's article for insight into changing them.
DoleParams::smile:oleParams() {
A = .00150;
Alpha = 5;
Beta = 0.5;
Eccentricity = 0.15;
Gamma = 1 / 3.0;
K = 50;
MassSol = 1;
MassSun = MassSol * 1.0;
MassNuclei = MassSun * 1e-15;
B = MassSun * 1.2e-5;
W = .2;
}

// Return disk density at orbital distance au
double DoleParams::density(double au) {
return A * exp(-Alpha * pow(au, Gamma));
}

// Returns dust+gas density swept at orbital distance AU by a body of given
//  ratio (critical mass / mass).
double DoleParams::gasdensity(double au, double massratio) {
return (K * density(au)) / (1 + sqrt(massratio) * (K-1));
}

// Return critical mass to form a gas giant at orbital distance au
double DoleParams::masscritical(double au) {
return B * pow(au, -.75);
}

// I don't know what these functions really *represent*, but they're
//  repeatedly used.
double DoleParams::lowbound(double radius, double margin) {
return radius - margin - W * (radius - margin) / (1 + W);
}

double DoleParams::highbound(double radius, double margin) {
return radius + margin + W * (radius + margin) / (1 - W);
}

const double epsilon = .00001;
const int Maxint = 32767;

int imin(int a, int b = Maxint, int c = Maxint) {
if (a < b)
return (a < c) ? a : c;
else
return (b < c) ? b : c;
}

int imax(int a, int b = -Maxint, int c = -Maxint) {
if (a > b)
return (a > c) ? a : c;
else
return (b > c) ? b : c;
}

ofstream *Ps;
int PsPage = 1;
double
PsBase = 50,            // base offset from lower left corner
PsXscale = 1, PsYscale = 1,
PsXoff = 0, PsYoff = 0;

void ps_end() {
*Ps << "%%Trailer\nend" << flush;
delete Ps;

Ps = NULL;
}

void ps_beginpage(int page) {
*Ps << "%%Page: " << page << ' ' << page << '\n'
<< PsXoff+PsBase << ' ' << PsYoff+PsBase << " translate\n"
<< PsXscale << ' ' << PsYscale << ' ' << " scale\n"
<< "/Helvetica findfont " << 9 / PsXscale << " scalefont setfont\n"
<< "0 setlinewidth\n";
}

void ps_begin(const char *file) {
if (Ps != NULL) {
ps_end();
}

Ps = new ofstream(file);
<< "%%Pages: 3\n"
<< "/Helvetica findfont 12 scalefont setfont\n"
<< "0 setlinewidth\n"
<< "newpath\n"
<< "%%EndProlog\n";

ps_beginpage(PsPage++);
}

void ps_showpage() {
*Ps << "showpage\n";
ps_beginpage(PsPage++);
}

void ps_window(double x1, double y1, double x2, double y2)
{
const double width = 450;
double
xspan = x2 - x1, yspan = y2 - y1;

PsXscale = width / xspan;
PsYscale = PsXscale;        // could be width / yspan
PsXoff   = -PsXscale * x1;
PsYoff   = -PsYscale * y1;
}

void ps_circle(double x, double y, double radius, int fill)
{
*Ps << x << ' ' << y << ' ' << radius << " 0 360 arc ";
*Ps << (fill ? "fill" : "stroke") << endl;
}

void randomize(long seed)
{
srand48(seed);
}

double rand01()
{
return drand48();
}
#ifdef sun
double        sqr(double x)
{
return x * x;
}
#endif

// Return closest integer to arg
int cint(double arg)
{
return int(floor(arg+0.5));
}

void ps_pset(double x, double y)
{
ps_circle(x, y, 0.01, 0);
}

void ps_line(double x1, double y1, double x2, double y2)
{
*Ps << x1 << ' ' << y1 << " moveto "
<< x2 << ' ' << y2 << " lineto stroke\n";
}

void ps_text(double x, double y, const char *s) {
*Ps << x << ' ' << y << " moveto (" << s << ") show newpath\n";
}

// Draw scale on figure
void logscale(const char *xlabel = "", const char *ylabel = "") {
ps_line(-1, -1,  3, -1);
ps_line( 3, -1,  3,  1);
ps_line( 3,  1,  3, -1);
ps_line( 3, -1, -1, -1);

ps_line(-1, 1, 3, 1);
for (double au = 1; au <= 10 + epsilon; au += 1)  {
ps_line(log10(au/10), 1, log10(au/10), .95);
ps_line(log10(au), 1, log10(au), .95);
ps_line(log10(au*10), 1, log10(au*10), .95);
}

ps_text(-1, 1, ".1");
ps_text( 0, 1, "1");
ps_text( 1, 1, "10");
ps_text( 2, 1, "100");

ps_text(2.3, 1, xlabel);
ps_text(-1, .9, ylabel);
}

struct Nucleus {
double
axis,        // semimajor axis of the nuclei orbit
eccen,        // eccentricity of the nuclei orbit
mass,        // mass of the nuclei
pAttr,        // grav attract dist at perigee
aAttr;        // grav attract dist at apogee
enum {
Rock, GasGiant
}    type;        // type of planet

Nucleus();
void dump(int, ostream &, DoleParams *);
friend int nucleusCompare(void *, void *);
double lowbound(DoleParams *params);
double highbound(DoleParams *params);
};

Nucleus::Nucleus() {
axis = eccen = mass = 0;
pAttr = aAttr = 0;
type = Rock;
}

double Nucleus::lowbound(DoleParams *params) {
}

double Nucleus::highbound(DoleParams *params) {
}

// Comparison function used by qsort()
#ifdef __GNUG__
int nucleusCompare(void *p1, void *p2) {    // g++ is broken again
#else
int nucleusCompare(const void *p1, const void *p2) {
#endif
double  r1 = ((const Nucleus *)p1)->axis,
r2 = ((const Nucleus *)p2)->axis;
return (r1 < r2) ? -1 : (r1 > r2) ? 1 : 0;
}

// Dump nucleus stats to specified stream
void Nucleus::dump(int num, ostream &o, DoleParams *params) {
double
lowrange = lowbound(params),
highrange = highbound(params),

o << "Nucleus " << num << '\n';
o << "\tRadius\t\t" << axis << '\n';
o << "\tEccentricity\t" << eccen << '\n';
o << "\tMass\t\t" << mass << '\n';
o << "\tType\t\t" << ((type == GasGiant) ? "Gas giant" : "Rocky") << '\n';

o << "\tRange          = [" << lowrange << "," << highrange << "]\n";
o << "\tX[pa]limit     = [" << xplimit << "," << xalimit << "]\n";
o << "\tX{peri,apo}gee = [" << pAttr << "," << aAttr << "]\n";
o << "\tR{peri,apo}gee = [" << pRad << "," << aRad << "]\n";

o << "\tCritical Mass  = " << massCrit << ' '
<< '(' << mass / massCrit << "% of Critical)\n";
}

main(int ac, char *av[])
{
int dumpflag = 0, nplanet = 0;
long seed = time(0);

for (int i = 0; i < ac; i++) {
if (!strcmp(av[i], "-dump"))
dumpflag = 1;
else if (!strcmp(av[i], "-seed"))
seed = atoi(av[++i]);
}

double  nucleieccent, nucleiradius, masslast, eccent, mass, rperigee,
rapogee, radius, mu, xperigee, xapogee, masscritical,
density, bw, volume, da, dp, masspass;

int     n, lowband, highband, dustcheck, iterate, hit;

// Bands are measured in 1/5 au intervals from 0 to 50 AU
const int
MaxBand = 50 * 5,
MaxNuclei = 1000;   // Max # of nuclei to inject

enum { Mixed, Gas, Empty }
band[MaxBand+1];    // Contents of band

int MaxPlanets = 20;    // Size of dynamic array containing generated planets
Nucleus *nuclei = new Nucleus[MaxPlanets];
DoleParams *params = new DoleParams;

randomize(seed);

ps_window(-1, -1, 2, 1);
ps_begin("planets.ps");

// Set up primordial cloud
// Dust within .3 au is swept out by radiation pressure

for (n = 1; n <= MaxBand; n++)
band[n] = Mixed;

// Draw scale on figure
logscale("AU");

// Plot density function; space samples even on log scale
double max = 1.1 * params->density(0.3), lastau, lastrho,
logaumin = log10(0.3),
logaumax = log10(50),
logaustep = (logaumax - logaumin) / 100;

for (double logau = logaumin; logau <= logaumax; logau += logaustep) {
double rho = params->density(pow(10,logau)) / max;

if (logau > logaumin)
ps_line(lastau, lastrho, logau, rho);
else {
char buf[100];
sprintf(buf, "density [max = %g]", max);
ps_text(logau, rho, buf);
}

lastau = logau;
lastrho = rho;
}

double yBand = 0.9;
// Inject nuclei into the cloud and acrete gas and dust
for (n = 0; n < MaxNuclei; n++) {
// Check to see if all bands are taken
int bandfull = 0;
for (int i = 1; i <= MaxBand; i++) {
if (band[i] == Mixed)  {
bandfull = 1;
break;
}
}

if (bandfull == 0)
break;

nucleieccent = 1 - pow(1 - rand01(), .077);
while ((nucleiradius = rand01() * 50) < .3)
;

Nucleus body;
body.eccen = nucleieccent;
body.mass = params->MassNuclei;

// This is only used on first pass of nuclei
masslast = 0;
iterate = 0;

// Mass accumulation loop - continue until minimal accumulation
while (1) {
eccent = body.eccen;
mass = body.mass;
if (mass == 0)
mass = 1e-15;
mu = pow(mass / (1 + mass), .25);
body.pAttr = xperigee = rperigee * mu;
body.aAttr = xapogee = rapogee * mu;

// Nuclei sweeps up band
// Establish bounds on swept volume
lowband = cint(body.lowbound(params) * 5);
if (lowband < 1)
lowband = 1;

highband = cint(body.highbound(params) * 5);
if (highband < 1)
highband = 1;
else if (highband > MaxBand)
highband = MaxBand;

if (lowband == highband)
highband++;

// calculate critical mass limits
masscritical = params->masscritical(rperigee);

/* check for bands with dust within range */
if (iterate == 0) {
dustcheck = 0;

for (int bandno = lowband; bandno <= highband; bandno++) {
if (masslast > 0 || band[bandno] == Mixed) {
dustcheck = 1;
break;
}
}

// If no bands have dust in them (band[bandno] == Mixed), then dud
// dustcheck == 1 means dust is in some band

if (dustcheck == 0) {
// cout << "no dust; " << n << "is a dud" << endl;
if (masslast == 0) {
body.axis = 0;
body.eccen = 0;
body.mass = 0;
}
break;  // exit mass accumulation loop
}
}

// Calculate mass using Dole donut approximation
if (mass == 0)
mass = 1e-15;

if (mass < masscritical)
density = params->density(body.axis);
else {
// Sweeps dust and gas
density = params->gasdensity(body.axis, masscritical / mass);
}

bw = 2 * body.axis * body.eccen +
xapogee + xperigee +
params->W * (rapogee + xapogee) / (1 - params->W) +
params->W * (rperigee - xperigee) / (1 + params->W);

volume = 2 * M_PI * body.axis * bw * (xapogee + xperigee);

double sweepmass = volume * density;    // unused

dp = body.lowbound(params);
da = body.highbound(params);

lowband = cint(dp * 5);
highband = cint(da * 5);

if (lowband == highband)
highband++;
if (highband > MaxBand)
highband = MaxBand;
masspass = 0;

for (int bandno = lowband; bandno <= highband; bandno++) {
double
au = bandno / 5.0,
xpnow, xanow,
bandvol, bandvol2;

// Calculate mass of wedge of doughnut
xpnow = xperigee + (xapogee - xperigee) *
(bandno - lowband) / (double)(highband - lowband);
xanow = xperigee + (xapogee - xperigee) *
(bandno + 1 - lowband) / (double)(highband - lowband);

bandvol = 2 * M_PI * au * 0.2 * (xpnow + xanow);

for (i = 0; i < nplanet; i++) {
double
dp2 = nuclei[i].lowbound(params),
da2 = nuclei[i].highbound(params);

if (da2 < au || dp2 > au + 0.2)
continue;

// Overlap exists, find bounds on overlap volume
double
bw2 = 2 * nuclei[i].axis * nuclei[i].eccen +
nuclei[i].pAttr +
nuclei[i].aAttr +
params->W * (nuclei[i].aRad + nuclei[i].aAttr) / (1 - params->W) +
params->W * (nuclei[i].pRad - nuclei[i].pAttr) / (1 + params->W);

// At au, overlap has xp2now and xa2now as heights
double xp2now = nuclei[i].pAttr +
(nuclei[i].aAttr - nuclei[i].pAttr) *
(au - dp2) / (da2 - dp2);
double xa2now = nuclei[i].pAttr +
(nuclei[i].aAttr - nuclei[i].pAttr) *
(au + 0.2 - dp2) / (da2 - dp2);
bandvol2 = 2 * M_PI * au * 0.2 * (xp2now + xa2now);

// If previously swept band larger than this swept, no dust
//    swept up.

if (bandvol2 >= bandvol)
bandvol = 0;
else
bandvol -= bandvol2;
break;
}

masspass += bandvol * density;
}

body.mass = mass = masspass;

// cout << mass << ' ' << n << endl;

// check for mass growth convergence
if (mass == 0)
mass = 1e-15;
if (mass >= masscritical) {
body.type = Nucleus::GasGiant;
}
if (fabs(masslast / mass - 1) < 0.01)
break;
masslast = mass;

iterate = 1;
}   // end mass accumulation loop

// Clear out bands emptied of dust
if (dustcheck == 1) {
cout << "event " << n << " completed mass growth; swept from "
<< dp << " to " << da;
if (mass > masscritical)
cout << "(gas giant)";
cout << endl;
}
if (lowband == highband)
highband++;

for (int bandno = lowband; bandno <= highband; bandno++) {
if (mass < masscritical) {
if (band[bandno] == Mixed)
band[bandno] = Gas;
}

if (mass >= masscritical) {
// Clear out bands emptied by gas
body.type = Nucleus::GasGiant;
if (band[bandno] == Gas || band[bandno] == Mixed)
band[bandno] = Empty;
}
}

// cout << "check nucleus " << n << " for overlap and coalescence\n";

// check for orbital and  gravitational overlap and coalescence
// cout << body.pRad - body.pAttr

if (body.axis == 0)
continue;

hit = 0;
for (i = 0; i < nplanet; i++) {
double  newradius, newmass, neweccent, term1, term2, term3, munew;

// cout << n << '\t' << i << '\t'
//        << nuclei[i].aRad << '\t' << rperigee-xperigee << '\t'
//        << nuclei[i].pRad << '\t' << rapogee+xapogee << endl;

if ((nuclei[i].aRad < (rperigee - xperigee) &&
(nuclei[i].aRad + nuclei[i].aAttr) < rperigee) ||
((rapogee + xapogee) < nuclei[i].pRad &&
continue;

cout << "coalesence of nuclei  " << n << ' ' << i << endl;

hit = 1;
newradius = (body.mass + nuclei[i].mass) /
(body.mass / body.axis + nuclei[i].mass / nuclei[i].axis);

term1 = body.mass * sqrt(body.axis) * sqrt(1 - pow(body.eccen, 2));
term2 = nuclei[i].mass * sqrt(nuclei[i].axis) *
sqrt(1 - pow(nuclei[i].eccen, 2));
term3 = (body.mass + nuclei[i].mass) * sqrt(newradius);

neweccent = sqr(abs(1 - pow((term1 + term2) / term3, 2)));
newmass = body.mass + nuclei[i].mass;

// cerr << "Nuking body " << i << endl;
nuclei[i].axis = 0;
nuclei[i].eccen = 0;
nuclei[i].mass = 0;
body.eccen = neweccent;
body.mass = newmass;
munew = pow(newmass / (1 + newmass), .25);

//    cout << "new mass = " << newmass << " new radius = " << newradius
//         << " new eccentricity = " << neweccent << endl;
}

mass = body.mass;

// Show depletion of bands
int
lowband2 = cint(body.lowbound(params) * 5),
highband2 = cint(body.highbound(params) * 5);

lowband2 = imax(lowband2, 6, lowband);
highband2 = imax(imin(highband2, MaxBand, highband), 6);
if (lowband2 == highband2)
highband2++;

ps_line(log10(lowband2 * 0.2), yBand, log10(highband2 * 0.2), yBand);
yBand -= 0.01;

// iterate for mass captured
//  cout << "Start of iteration for mass" << N
//     << " mass = " << mass
//     << " masslast = " << masslast << endl;

if (body.mass >= masscritical)
body.type = Nucleus::GasGiant;

if (nplanet >= MaxPlanets) {
Nucleus *newplanet = new Nucleus[MaxPlanets*2];

// Copy old planets to new
for (int i = 0; i < nplanet; i++)
newplanet[i] = nuclei[i];

#ifdef __GNUG__
// Guess what? G++ doesn't implement this right either.
delete [MaxPlanets] nuclei;
#else
delete [] nuclei;        // Get rid of old planets
#endif
nuclei = newplanet;     // Use new planet array
MaxPlanets *= 2;        // New array size
}
nuclei[nplanet++] = body;

// Sweep planet array, removing merged bodies
for (i = 0; i < nplanet; i++) {
if (nuclei[i].axis == 0) {
//for (int j = i+1; j < nplanet; j++)
//    nuclei[j-1] = nuclei[j];
//nplanet--;
cerr << "Nuking body " << i << "; " << nplanet << " remaining" << endl;
nuclei[i] = nuclei[nplanet-1];
nuclei[nplanet-1].axis = 0;
nplanet--;
}
}
}

cout << " all bands taken....." << n << "   nuclei used     " << endl;
ps_text(2.2, 0.9, "emptied bands");

qsort((void *)nuclei, nplanet, sizeof(Nucleus), &nucleusCompare);

// Draw planets, gas giants as filled circles
double massSum = 0;
for (i = 0; i < nplanet; i++) {
massSum += nuclei[i].mass;

double au = log10(nuclei[i].axis), r = pow(nuclei[i].mass, 1/3.0);

ps_circle(au, 0, r, nuclei[i].type == Nucleus::GasGiant);
}
ps_showpage();

cout << "Total mass = " << massSum << endl;

if (dumpflag) {
cout << "Random number seed =" << seed << endl;

for (i = 0; i < nplanet; i++) {
if (nuclei[i].axis > 0)
nuclei[i].dump(i, cout, params);
}

cout << "Bands with dust still in them:\n";
for (i = 1; i <= MaxBand; i++)
if (band[i] == Mixed)
cout << i << ' ';
cout << endl;
}

ps_end();

return (0);
}
``````

My Conversion:

``````using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public enum BANDCONTENTS { MIXED, GAS, EMPTY }

public class CONSTANTS
{
public const float  Q           = 0.077f,
NUCLEIMASS  = 1E-15f,
EPSILON     = 0.00001f;

public const int    MAXBAND     = 50 * 5,
MAXNUCLEI   = 1000,
MAXPLANETS  = 20,
MAXINT      = 32767;
}

public class DoleParams
{
public float    A,
B,
alpha,
beta,
e,      //eccentricity
G,      //gamma
K,
mS,     //mass of the sun
mN,     //mass of the nucleus
W;

public DoleParams(float solarMass)
{
//Defaults...
A       = 0.0015f;
alpha   = 5.0f;
beta    = 0.5f;
e       = 0.15f;
G       = 1.0f / 3.0f;
K       = 50.0f;
mS      = solarMass;
mN      = mS * CONSTANTS.NUCLEIMASS;
B       = mS * 1.2E-5f;
W       = 0.2f;
}

public float LowBound(float rP, float aP)
{
return rP - aP - W * (rP - aP) / (1 + W);
}

public float HighBound(float rA, float aA)
{
return rA + aA + W * (rA + aA) / (1 - W);
}

public float CriticalMass(float au)
{
return B * Mathf.Pow(au, -0.75f);
}

public float Density(float au)
{
return A * Mathf.Exp(-alpha * Mathf.Pow(au, G));
}

public float GasDensity(float au, float massRatio)
{
return (K * Density(au)) / (1 + Mathf.Sqrt(massRatio) * (K - 1));
}
}

public class Nucleus
{
public float    a,  //semi-major axis
e,  //eccentricity
m,  //mass
rP, //perihelion distance
rA, //aphelion distance
aP, //perihelion atttraction limit
aA; //aphelion attraction limit
public enum NUCLEUSTYPE { ROCK, GASGIANT };
public NUCLEUSTYPE nucleusType;

public Nucleus()
{
a = e = m = 0.0f;
rP = rA = 0.0f;
aP = aA = 0.0f;
nucleusType = NUCLEUSTYPE.ROCK;
}

public float LowBound(DoleParams param)
{
return param.LowBound(rP, aP);
}

public float HighBound(DoleParams param)
{
return param.HighBound(rA, aA);
}
}

public class SolarSystem : MonoBehaviour
{
public int seed = 41;

public float solarMass = 1.0f;

void Start()
{
float   nE  = 0,     //nucleus eccentricity
nA  = 0,     //nucleus semi-major axis
mL  = 0,     //mass last
e   = 0,     //eccentricity
m   = 0,     //mass
rP  = 0,     //perihelion distance, au
rA  = 0,     //aphelion distance, au
a   = 0,     //semi-major axis
mu  = 0,     //mass-unit
aP  = 0,
aA  = 0,
mC  = 0,     //critical mass
rho = 0,     //density
bw  = 0,     //bandwidth
v   = 0,     //volume
dA  = 0,
dP  = 0,
mP  = 0;     //mass pass

int     nPlanet     = 0,
n,
lowBand,
highBand,
dustCheck   = 0,
iterate,
hit;

int MaxPlanets = CONSTANTS.MAXNUCLEI;
Nucleus[] nuclei = new Nucleus[MaxPlanets];
DoleParams param = new DoleParams(solarMass);

BANDCONTENTS[] band = new BANDCONTENTS[CONSTANTS.MAXBAND + 1];

Randomize(seed);

//Initialize the Bands...
for (n = 1; n <= CONSTANTS.MAXBAND; n++)
{
band[n] = BANDCONTENTS.MIXED;
}
//Inject nuclei into the cloud and acrete gas and dust
for (n = 0; n < CONSTANTS.MAXNUCLEI; n++)
{
//Check to see if all bands are taken
int bandFull = 0;

for (int i = 1; i <= CONSTANTS.MAXBAND; i++)
{
if (band[i] == BANDCONTENTS.MIXED)
{
bandFull = 1;
break;
}
}

if (bandFull == 0)
{
break;
}

//If the Semi-Major Axis of the Nucleus is < 0.3f, then iterate until it isn't.
while ((nA = 50.0f * RandomNumber()) < 0.3f) ;
//Calculate the Eccentricity of the Nucleus
nE = 1.0f - (1.0f - Mathf.Pow(1.0f - RandomNumber(), CONSTANTS.Q));

Nucleus body = new Nucleus();
body.a = nA;
body.e = nE;
body.m = param.mN;

mL = 0.0f;
iterate = 0;

while (true) //while Loop
{
a = body.a;
e = body.e;
m = body.m;
body.rP = rP = nA - (nA * nE);
body.rA = rA = nA + (nA * nE);

if (m == 0)
{
m = CONSTANTS.NUCLEIMASS;
}

mu = Mathf.Pow(m / (1.0f + m), 0.25f);
body.aP = aP = rP * mu;
body.aA = aA = rA * mu;

//Nuclei sweeps up band
//Establish the bounds on swept volume
lowBand = cint(body.LowBound(param) * 5);
if (lowBand < 1)
{
lowBand = 1;
}

highBand = cint(body.HighBound(param) * 5);
if (highBand > CONSTANTS.MAXBAND)
{
highBand = CONSTANTS.MAXBAND;
}

if (lowBand == highBand)
{
highBand++;
}

//Calculate critical mass limits
mC = param.CriticalMass(rP);

//Check for bands with dust within range
if (iterate == 0)
{
dustCheck = 0;

for (int bandno = lowBand; bandno <= highBand; bandno++)
{
if (mL > 0 || band[bandno] == BANDCONTENTS.MIXED)
{
//Debug.Log("Band #" + bandno + " has dust...");
dustCheck = 1;
break;
}
}

if (dustCheck == 0)
{
//Debug.Log("No dust; " + n + " is a dud...");
if (mL == 0)
{
body.a = 0.0f;
body.e = 0.0f;
body.m = 0.0f;
}
break;
}
}

//Calculate mass using Dole donut approximation
if (m == 0)
{
m = CONSTANTS.NUCLEIMASS;
}

if (m < mC)
{
//Debug.Log("param.Density()");
rho = param.Density(body.a);
}
else
{
//Debug.Log("param.GasDensity()");
rho = param.GasDensity(body.a, mC / m);
}

bw =        2 * body.a * body.e
+   aA + aP
+   param.W * (rA + aA) / (1 - param.W)
+   param.W * (rP - aP) / (1 + param.W);

v = 2 * Mathf.PI * body.a * bw * (aA + aP);

dP = body.LowBound(param);
dA = body.HighBound(param);

lowBand = cint(dP * 5);
highBand = cint(dA * 5);

if (lowBand == highBand)
{
highBand++;
}

if (highBand > CONSTANTS.MAXBAND)
{
highBand = CONSTANTS.MAXBAND;
}

mP = 0.0f;

for (int bandno = lowBand; bandno <= highBand; bandno++)
{
float au = bandno / 5.0f,
xPnow,
xAnow,
bandVol,
bandVol2;

//Calculate mass of wedge of doughnut
xPnow =     aP + (aA - aP)
*   (bandno - lowBand) / (float)(highBand - lowBand);
//Debug.Log("xPnow: " + xPnow);
xAnow =     aP + (aA - aP)
*   (bandno + 1 - lowBand) / (float)(highBand - lowBand);
//Debug.Log("xAnow: " + xAnow);

bandVol = 2 * Mathf.PI * au * 0.2f * (xPnow + xAnow);
//Debug.Log("Band Volume = " + bandVol);

for (int i = 0; i < nPlanet; i++)
{
float dP2 = nuclei[i].LowBound(param);
//Debug.Log("dP2 = " + dP2);
float dA2 = nuclei[i].HighBound(param);
//Debug.Log("dA2 = " + dA2);

if (dA2 < au || dP2 > au + 0.2f)
{
//Debug.Log("dA2 < au OR dP2 > au + 0.2f");
continue;
}

//Overlap exists, find bounds on the overlap volume
float bw2 =     2 * nuclei[i].a * nuclei[i].e
+   nuclei[i].aP + nuclei[i].aA
+   param.W * (nuclei[i].rA + nuclei[i].aA) / (1 - param.W)
+   param.W * (nuclei[i].rP - nuclei[i].aP) / (1 + param.W);
//Debug.Log("Bandwidth 2 = " + bw2);

//At au, overlap has xP2now and xA2now as heights
float xP2now =      nuclei[i].aP + (nuclei[i].aA - nuclei[i].aP)
*   (au - dP2) / (dA2 - dP2);
//Debug.Log("xP2now = " + xP2now);
float xA2now =      nuclei[i].aP + (nuclei[i].aA - nuclei[i].aP)
*   (au + 0.2f - dP2) / (dA2 - dP2);
//Debug.Log("xA2now = " + xA2now);

bandVol2 = 2 * Mathf.PI * au * 0.2f * (xP2now + xA2now);
//Debug.Log("Band Volume 2 = " + bandVol2);

//If previously swept band larger than this swept, no dust swept
if (bandVol2 >= bandVol)
{
//Debug.Log("bandVol2 >= bandVol");
bandVol = 0.0f;
}
else
{
//Debug.Log("bandVol2 ! >= bandVol");
bandVol -= bandVol2;
}
break;
}
mP += bandVol * rho;
//Debug.Log("Mass pass = " + mP);
}
//Debug.Log("--------------------");

body.m = m = mP;

//Check for mass growth convergence
if (m == 0)
{
//Debug.Log("Mass is 0!");
m = CONSTANTS.NUCLEIMASS;
}

if (m >= mC)
{
//Debug.Log("Nucleus " + n + " is a Gas Giant!");
body.nucleusType = Nucleus.NUCLEUSTYPE.GASGIANT;
}

if (Mathf.Abs(mL / m - 1) < 0.01f)
{
//Debug.Log("(mL / m - 1) < 0.01");
break;
}

mL = m;
//Debug.Log("Mass Last = " + mL);

iterate = 1;
}

//Clear out bands emptied of dust
if (dustCheck == 1)
{
Debug.Log("Event #" + n + " Completed mass growth; swept from " + dP + " to " + dA);
if (m > mC)
{
Debug.Log("Gas Giant!");
}
}

if (lowBand == highBand)
{
highBand++;
}

for (int bandno = lowBand; bandno <= highBand; bandno++)
{
if (m < mC)
{
if (band[bandno] == BANDCONTENTS.MIXED)
{
//Debug.Log("Band #" + bandno + " contains GAS only...");
band[bandno] = BANDCONTENTS.GAS;
}
}

if (m >= mC)
{
//Clear the bands emptied by gas
body.nucleusType = Nucleus.NUCLEUSTYPE.GASGIANT;
if (band[bandno] == BANDCONTENTS.GAS || band[bandno] == BANDCONTENTS.MIXED)
{
//Debug.Log("Band #" + bandno + " is EMPTY...");
band[bandno] = BANDCONTENTS.EMPTY;
}
}
}

if (body.a == 0.0f)
{
//Debug.Log("Body #" + n + " Axis was 0...");
continue;
}

/*hit = 0;
for (int i = 0; i < nPlanet; i++)
{
float aNew,
mNew,
eNew,
term1,
term2,
term3,
muNew;

if ((nuclei[i].rA < (rP - aP) && (nuclei[i].rA + nuclei[i].aA) < rP) || ((rA + aA) < nuclei[i].rP && rA < (nuclei[i].rP - nuclei[i].aP)))
{
continue;
}

Debug.Log("Coalescence of nuclei #" + n + " " + i);

hit = 1;
aNew = (body.m + nuclei[i].m) / (body.m / body.a + nuclei[i].m / nuclei[i].a);
//Debug.Log("aNew = " + aNew);
term1 = body.m * Mathf.Sqrt(body.a) * Mathf.Sqrt(1 - Mathf.Pow(body.e, 2.0f));
//Debug.Log("term1 = " + term1);
term2 = nuclei[i].m * Mathf.Sqrt(nuclei[i].a) * Mathf.Sqrt(1 - Mathf.Pow(nuclei[i].e, 2.0f));
//Debug.Log("term2 = " + term1);
term3 = (body.m + nuclei[i].m) * Mathf.Sqrt(aNew);
//Debug.Log("term3 = " + term1);

eNew = Mathf.Sqrt(Mathf.Abs(1 - Mathf.Pow((term1 + term2) / term3, 2.0f)));
//Debug.Log("New Eccentricity = " + eNew);
mNew = body.m + nuclei[i].m;
//Debug.Log("New Mass = " + mNew);

nuclei[i].a = 0.0f;
nuclei[i].e = 0.0f;
nuclei[i].m = 0.0f;
body.a = aNew;
body.e = eNew;
body.m = mNew;
//body.rP = aNew - (aNew * eNew);
body.rP = aNew * (1 - eNew);
//Debug.Log("body.rP = " + body.rP);
body.rA = aNew * (1 + eNew);
//body.rA = aNew + (aNew * eNew);
//Debug.Log("body.rA = " + body.rA);
muNew = Mathf.Pow(mNew / (1 + mNew), 0.25f);
body.aP = body.rP * muNew;
body.aA = body.rA * muNew;
} //End: (int i = 0; i < nPlanet; i++)

m = body.m;

int lowBand2 = cint(body.LowBound(param) * 5),
highBand2 = cint(body.HighBound(param) * 5);

lowBand2 = Mathf.Max(lowBand2, 6, lowBand);
//lowBand2 = imax(lowBand2, 6, lowBand);
//Debug.Log("lowBand2 = " + lowBand2);
highBand2 = Mathf.Max(Mathf.Min(highBand2, CONSTANTS.MAXBAND, highBand), 6);
//highBand2 = imax(imin(highBand2, CONSTANTS.MAXBAND, highBand), 6);
//Debug.Log("highBand2 = " + highBand2);

if (lowBand2 == highBand2)
{
highBand2++;
}

if (body.m >= mC)
{
body.nucleusType = Nucleus.NUCLEUSTYPE.GASGIANT;
}

if (nPlanet >= MaxPlanets)
{
Nucleus[] newNuclei = new Nucleus[MaxPlanets * 2];

//Copy old planets to new
for (int i = 0; i < nPlanet; i++)
{
newNuclei[i] = nuclei[i];
}

nuclei = null;

nuclei = newNuclei;
MaxPlanets *= 2;
//Debug.Log("Max Planets = " + MaxPlanets);
}

nuclei[nPlanet++] = body;
//Debug.Log("nPlanet: " + nPlanet);

//Sweep planet array, removing merged bodies
for (int i = 0; i < nPlanet; i++)
{
if (nuclei[i].a == 0)
{
Debug.Log("Nuking body " + i + "; " + nPlanet + " remaining");
nuclei[i] = nuclei[nPlanet - 1];
nuclei[nPlanet - 1].a = 0.0f;
nPlanet--;
}
}*/

} //End: for (n = 0; n < CONSTANTS.MAXNUCLEI; n++)

Debug.Log("--------------------");
Debug.Log(" all bands taken..." + n + " nuclei used ");

//OuputPlanetData(nuclei, band);
}

//Seed the Random Number Generator
void Randomize(int seed)
{
Random.seed = seed;
}
//Return a Random Number
float RandomNumber()
{
return Random.value;
}

int cint(float arg)
{
return (int)(Mathf.Floor(arg + 0.5f));
}

int imax(int a, int b = -CONSTANTS.MAXINT, int c = -CONSTANTS.MAXINT)
{
if (a > b)
return (a > c) ? a : c;
else
return (b > c) ? b : c;
}

int imin(int a, int b = CONSTANTS.MAXINT, int c = CONSTANTS.MAXINT)
{
if (a < b)
return (a < c) ? a : c;
else
return (b < c) ? b : c;
}

void OuputPlanetData(Nucleus[] nuclei, BANDCONTENTS[] band)
{
//for (int i = 0; i < nuclei.Length; i++)
//{
//    Debug.Log("Planet #" + i + " Semi-Major Axis: " + nuclei[i].a);
//    Debug.Log("Planet #" + i + " Type: " + nuclei[i].nucleusType);
//}

for (int i = 0; i < CONSTANTS.MAXBAND; i++)
{
Debug.Log("Band #" + i + ", Contents =" + band[i]);
}
}
}
``````

Definitely need the smart guys to help trouble shoot why this isn’t working as it should be. I imagine it has something to do with my limited understanding of C# and some kind of stupid error on my part.

I’ve re-read the paper that the code is based on, and I’m pretty sure things are ordered right…just, something is breaking in the coalescence and event handling…

Bump…anyone see anything obvious or anything at all?

As this accretion stuff is different to the original topic of this thread, I suggest you start a new thread. Only in the hope that you get more traffic and help regarding these conversions, as regulars who have seen this title will probably skip over and not see your new question (or just scan the page and see the planet mesh stuff). There are plenty of clever people here, just want you to get their attention for the optimal amount of help! Including C++ to C# in the title will also help.
Best of Luck.

EDIT :
Add the link to the accretion code conversion question in your first post so people can jump straight to the relevant post (found by clicking the #number next to the like/reply) : Procedural Planet Terrain Engine - Unity Engine - Unity Discussions

1 Like

Done and done! I’m not abandoning the noise work, just need to get the Solar System generation working before I go back to the noise bits, so…stay tuned Alucardj! You’ve been a great help!