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