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;
    //Define the planets radius
    public int planetRadius = 10;

    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;
    
        SurfacePatch patch = patchObject.AddComponent<SurfacePatch>();
        patch.InitPatchGenerator(lowLeft, topLeft, topRight, lowRight, this, index, null, sX, sZ);
        patch.GeneratePatchCoordinates(patchSubdivisions, patchResolution);
        patch.Add(patch);
    }



    /*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)
        {
            MeshFilter meshFilter = gameObject.AddComponent<MeshFilter>();
            patch = meshFilter.sharedMesh = new Mesh();

            gameObject.AddComponent<MeshRenderer>();
            GetComponent<Renderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
            GetComponent<Renderer>().receiveShadows = true;
            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]);
                patchVertices[vertIndex] *= _planet.planetRadius;
                //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]);
                patchVertices[vertIndex] *= _planet.planetRadius;
                //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]);
                patchVertices[vertIndex] *= _planet.planetRadius;
                //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]);
                patchVertices[vertIndex] *= _planet.planetRadius;
                //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]);
                patchVertices[vertIndex] *= _planet.planetRadius;
                //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]);
                patchVertices[vertIndex] *= _planet.planetRadius;
                //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 void Add(SurfacePatch 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]);
    }*/
}

V/R

Brad

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;
    //Define the planets radius
    public int planetRadius = 10;

    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.gameObject.AddComponent<MeshRenderer>();
        //patchObject.GetComponent<Renderer>().material = base.GetComponent<Renderer>().material;
        //Add a MeshCollider for Collision purposes
        patchObject.AddComponent<MeshCollider>();

        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;
        }

        SurfacePatch patch = patchObject.AddComponent<SurfacePatch>();
        //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);
        //patch.Add(patch);
        //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)
        {
            MeshFilter meshFilter = gameObject.AddComponent<MeshFilter>();
            patch = meshFilter.sharedMesh = new Mesh();

            GetComponent<Renderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
            GetComponent<Renderer>().receiveShadows = true;
            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;
        float noiseBase = _planet.planetRadius;

        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);

                patchVertices[vertIndex] *= _planet.planetRadius + noise;

                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);

                patchVertices[vertIndex] *= _planet.planetRadius + noise;

                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);

                patchVertices[vertIndex] *= _planet.planetRadius + noise;

                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;
                //noise = Mathf.Clamp(noise, _planet.planetRadius + noise, _planet.planetRadius + noise + 1.0f);
    
                patchVertices[vertIndex] *= _planet.planetRadius + noise;

                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);

                patchVertices[vertIndex] *= _planet.planetRadius + noise;

                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);

                patchVertices[vertIndex] *= _planet.planetRadius + noise;

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

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

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

    public void Add(SurfacePatch patch)
    {

    }

    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;
        float noiseBase = _planet.planetRadius;
                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);
                patchVertices[vertIndex] *= _planet.planetRadius + noise;
        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];
        float noiseBase = _planet.planetRadius;

        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);
                    }
                }
               

                patchVertices[vertIndex] *= _planet.planetRadius + noise[vertIndex];
                //patchVertices[vertIndex] *= _planet.planetRadius;

                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)
        {
            MeshFilter meshFilter = gameObject.AddComponent<MeshFilter>();
            patch = meshFilter.sharedMesh = new Mesh();

            GetComponent<Renderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
            GetComponent<Renderer>().receiveShadows = true;
            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);
        float noiseBase = _planet.planetRadius;

        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);
                    }
                }
               

                patchVertices[vertIndex] *= _planet.planetRadius + noise[vertIndex];
                //patchVertices[vertIndex] *= _planet.planetRadius;

                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);
                    }
                }
               
                patchVertices[vertIndex] *= _planet.planetRadius + noise[vertIndex];
                //patchVertices[vertIndex] *= _planet.planetRadius;

                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);
                    }
                }
               
                patchVertices[vertIndex] *= _planet.planetRadius + noise[vertIndex];
                //patchVertices[vertIndex] *= _planet.planetRadius;

                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);
                    }
                }
               
                patchVertices[vertIndex] *= _planet.planetRadius + noise[vertIndex];
                //patchVertices[vertIndex] *= _planet.planetRadius;

                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);
                    }
                }
               
                patchVertices[vertIndex] *= _planet.planetRadius + noise[vertIndex];
                //patchVertices[vertIndex] *= _planet.planetRadius;

                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);
                    }
                }
               
                patchVertices[vertIndex] *= _planet.planetRadius + noise[vertIndex];
                //patchVertices[vertIndex] *= _planet.planetRadius;

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

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

        vertIndex++;
    }

    public void Add(SurfacePatch patch)
    {

    }

    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 :smile:

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
{
    public float planetRadius = 10.0f;

    //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
                vertices[index] *= planetRadius;
            }
        }
        //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;
        //Add the MeshRenderer
        surface.gameObject.AddComponent<MeshRenderer>();
        //Add the MeshCollider
        surface.gameObject.AddComponent<MeshCollider>();
        //Add the MeshFilter
        surface.gameObject.AddComponent<MeshFilter>();

        //Initialize the Renderer
        surface.GetComponent<Renderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
        surface.GetComponent<Renderer>().receiveShadows = true;
        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
  4. Add that noise to the radius.

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
{
    public float planetRadius = 10.0f;

    //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()
    {
        //gameObject.AddComponent<MeshFilter>();

        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
                vertices[index] *= planetRadius + noise[index];
            }
        }
        //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;
        //Add the MeshRenderer
        surface.gameObject.AddComponent<MeshRenderer>();
        //Add the MeshCollider
        surface.gameObject.AddComponent<MeshCollider>();
        //Add the MeshFilter
        surface.gameObject.AddComponent<MeshFilter>();

        //Initialize the Renderer
        surface.GetComponent<Renderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
        surface.GetComponent<Renderer>().receiveShadows = true;
        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;

        meshList.Add(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


   public float planetRadius = 10.0f;

   //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
         vertices[index] *= planetRadius + noise;
       }
     }
     //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;
     //Add the MeshRenderer
     surface.gameObject.AddComponent<MeshRenderer>();
     //Add the MeshCollider
     surface.gameObject.AddComponent<MeshCollider>();
     //Add the MeshFilter
     surface.gameObject.AddComponent<MeshFilter>();
  
     //Initialize the Renderer
     surface.GetComponent<Renderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
     surface.GetComponent<Renderer>().receiveShadows = true;
     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;

   public Gradient planetColours;


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


   void Start()
   {
     CreateColourGradient();
     CreateSubdivisionSurfaces();
   }


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


   public float planetRadius = 10.0f;

   //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;
     //Add the MeshRenderer
     surface.gameObject.AddComponent<MeshRenderer>();
     //Add the MeshCollider
     surface.gameObject.AddComponent<MeshCollider>();
     //Add the MeshFilter
     surface.gameObject.AddComponent<MeshFilter>();
  
     //Initialize the Renderer
     surface.GetComponent<Renderer>().shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
     surface.GetComponent<Renderer>().receiveShadows = true;
     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


   void CreateColourGradient()
   {
     // using colour scale from LibNoise example : http://libnoise.sourceforge.net/tutorials/tutorial3.html
     //renderer.AddGradientPoint (-1.0000, utils::Color (  0,  0, 128, 255)); // deeps
     //renderer.AddGradientPoint (-0.2500, utils::Color (  0,  0, 255, 255)); // shallow
     //renderer.AddGradientPoint ( 0.0000, utils::Color (  0, 128, 255, 255)); // shore
     //renderer.AddGradientPoint ( 0.0625, utils::Color (240, 240,  64, 255)); // sand
     //renderer.AddGradientPoint ( 0.1250, utils::Color ( 32, 160,  0, 255)); // grass
     //renderer.AddGradientPoint ( 0.3750, utils::Color (224, 224,  0, 255)); // dirt
     //renderer.AddGradientPoint ( 0.7500, utils::Color (128, 128, 128, 255)); // rock
     //renderer.AddGradientPoint ( 1.0000, utils::Color (255, 255, 255, 255)); // snow
  
     planetColours = new Gradient();
  
     GradientColorKey[] gck = new GradientColorKey[8];
     gck[0].color = CalculateGradientColour(  0,  0, 128 );
     gck[0].time = CalculateGradientTime( -1.0000 );
     gck[1].color = CalculateGradientColour(  0,  0, 255 );
     gck[1].time = CalculateGradientTime( -0.2500 );
     gck[2].color = CalculateGradientColour(  0, 128, 255 );
     gck[2].time = CalculateGradientTime(  0.0000 );
     gck[3].color = CalculateGradientColour(  240, 240,  64 );
     gck[3].time = CalculateGradientTime(  0.0625 );
     gck[4].color = CalculateGradientColour(  32, 160,  0 );
     gck[4].time = CalculateGradientTime(  0.1250 );
     gck[5].color = CalculateGradientColour( 224, 224,  0 );
     gck[5].time = CalculateGradientTime(  0.3750 );
     gck[6].color = CalculateGradientColour( 128, 128, 128 );
     gck[6].time = CalculateGradientTime(  0.7500 );
     gck[7].color = CalculateGradientColour( 255, 255, 255 );
     gck[7].time = CalculateGradientTime(  1.0000 );
  
     GradientAlphaKey[] gak = new GradientAlphaKey[2];
     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 );
   }

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

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

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 );

then add this function :
   //   -------------------------------------------------------  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 :smile:
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)
2634676–185253–VertexColored.shader (985 Bytes)

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;
        body.GetComponent<Rigidbody>().AddForce(targetDirection * gravity, ForceMode.Acceleration);
    }
}


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 .
TL;DR: your code works :smile:

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);
    double lowbound(double radius, double margin);
    double highbound(double radius, double margin);
};

// 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);
    *Ps << "%!PS-Adobe-2.1\n"
    << "%%Pages: 3\n"
    << "%%EndComments\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
    pRad,        // orbital radius at perigee
    aRad,        // orbital radius at apogee
    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;
    pRad = aRad = 0;
    pAttr = aAttr = 0;
    type = Rock;
}

double Nucleus::lowbound(DoleParams *params) {
    return params->lowbound(pRad, pAttr);
}

double Nucleus::highbound(DoleParams *params) {
    return params->highbound(aRad, aAttr);
}

// 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
    xplimit = pRad - pAttr,
    xalimit = aRad + aAttr,
    lowrange = lowbound(params),
    highrange = highbound(params),
    massCrit = params->masscritical(pRad);

    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.axis = nucleiradius;
    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) {
        radius = body.axis;
        eccent = body.eccen;
        mass = body.mass;
        body.pRad = rperigee = nucleiradius * (1 - nucleieccent);
        body.aRad = rapogee = nucleiradius * (1 + nucleieccent);
        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
    //    << body.aRad+body.aAttr << endl;

    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 &&
         rapogee < (nuclei[i].pRad - nuclei[i].pAttr)))
        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.axis = newradius;
        body.eccen = neweccent;
        body.mass = newmass;
        body.pRad = newradius * (1 - neweccent);
        body.aRad = newradius * (1 + neweccent);
        munew = pow(newmass / (1 + newmass), .25);
        body.pAttr = body.pRad * munew;
        body.aAttr = body.aRad * munew;

        //    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;

    // Add new planet
    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");

    // Sort nuclei by radius
    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;
            }

            //Add new Planet
            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!