- Home /
Mapping Cube to Sphere - Using 64 planes as a cube "side"
I'm trying to make a procedural planet engine.
I've gone over tutorials teaching me how to map a 6 sided cube to a sphere using 6 different plane meshes. This I can do perfectly fine. However, I'd like the planets in my engine to use just a tad higher resolution than a single mesh plane can provide. I want to use 64x64 vertices per plane, 64x64 planes per cube side, versus 64x64 vertices on a single plane as a cube side. Thus, spherifying them is a little more difficult.
I currently generate gameobjects with their transform.position in the dead center of the plane model I generate for them, and align them to a cube shape, as seen in this image.
I then apply a spherification script I wrote/adapted from net sources for each of these planes. It adds the transform position of the plane's gameobject to the vertice it's working on to apply relativity to the planet's center. The result is this:
This is not the result I want, obviously, but I cannot for the life of me figure out where I went wrong. Any help would be appreciated.
For reference, my scripts:
GenMesh (manager script):
using UnityEngine;
using System.Collections;
using System;
public class GenMesh : MonoBehaviour {
int id;
Mesh mesh;
Spherify spheremaker;
CreatePlane maker;
GameObject[] meshList;
GameObject sideParent;
int i=0,k=0,side=0,index=0;
public Vector2 cubeSize;
public Vector2 tileSize;
public int vertDensity;
float xx,yy,zz;
Vector3 dir;
Material planeMat;
Mesh finalMesh;
ArrayList vertList,triList,uvList;
//Generation Variables
//Passes array; tracks the generation passes which have been completed.
//Guide: Initial Square Mesh Generation, Spherifying Operation, Mesh Collider Pass, N/A, N/A
bool[] passes={false,false,false,false,false};
int pass=0;
bool nextpass=false;
bool canrepeat=true;
float iterSpeed=0.001f;
// Use this for initialization
void Start () {
finalMesh=new Mesh();
vertList=new ArrayList();
triList=new ArrayList();
uvList=new ArrayList();
mesh=gameObject.GetComponent<MeshFilter>().mesh;
maker=GameObject.FindGameObjectWithTag("GameController").GetComponent<CreatePlane>();
spheremaker=GameObject.FindGameObjectWithTag("GameController").GetComponent<Spherify>();
sideParent=new GameObject();
sideParent.name="Side0";
planeMat=Resources.Load ("Materials/RockGround") as Material;
StartCoroutine("repeater");
meshList=new GameObject[(int)((cubeSize.x*cubeSize.y)*6)];
//mesh.RecalculateBounds();
//mesh.RecalculateNormals();
/*while(side<=5) {
makeMesh();
}*/
}
// Update is called once per frame
void Update () {
}
IEnumerator repeater() {
yield return new WaitForSeconds(iterSpeed);
switch(pass) {
case 0:
makeMesh();
if(side>5)
nextpass=true;
else
nextpass=false;
break;
case 1:
if(index==meshList.GetLength(0)-1)
{
//nextpass=true;
}
else
{
nextpass=true;
//combineMesh();
}
break;
case 2:
if(index>=meshList.GetLength(0)-1)
canrepeat=false;
else
{
nextpass=false;
spherify();
}
/*if(index==meshList.GetLength(0)-1)
{
nextpass=true;
canrepeat=false;
}
else
{*/
//nextpass=true;
//canrepeat=false;
//spherify();
//}
break;
}
index++;
if(nextpass==true) {
index=0;
i=0;
k=0;
pass++;
Debug.Log ("Pass "+pass+" finished.");
}
if(canrepeat==true) {
StartCoroutine("repeater");
}
}
void OnGUI() {
GUI.BeginGroup(new Rect(0,0,100,20));
GUI.Box (new Rect(0,0,100,20),id.ToString());
GUI.EndGroup();
}
void combineMesh() {
if(index>=meshList.GetLength(0)-1) {
finalMesh.vertices=(Vector3[])vertList.ToArray(typeof(Vector3));
finalMesh.triangles=(int[])triList.ToArray(typeof(int));
finalMesh.uv=(Vector2[])uvList.ToArray(typeof(Vector2));
mesh=finalMesh;
mesh.RecalculateBounds();
mesh.RecalculateNormals();
nextpass=true;
return;
}
MeshFilter reference=meshList[index].GetComponent<MeshFilter>();
vertList.AddRange(reference.mesh.vertices);
triList.AddRange(reference.mesh.triangles);
uvList.AddRange(reference.mesh.uv);
}
void spherify() {
iterSpeed=0.001f;
GameObject g=meshList[index];
MeshFilter meshFilter=g.GetComponent<MeshFilter>();
meshFilter.mesh=spheremaker.MapToPlanet(meshFilter.mesh,10000f,g.transform.position);
meshFilter.mesh.RecalculateBounds();
meshFilter.mesh.RecalculateNormals();
/*mesh=spheremaker.MapToPlanet(mesh,10000f,transform.position);
mesh.RecalculateBounds();
mesh.RecalculateNormals();*/
}
void applyMeshColliders() {
iterSpeed=1f;
GameObject g=meshList[index];
g.AddComponent(typeof(MeshCollider));
//index++;
}
void makeMesh() {
iterSpeed=0.001f;
if(i>cubeSize.x-1 && k==cubeSize.y-1)
{
side++;
k=0;
i=0;
if(side==6)
{
return;
}
else {
sideParent=new GameObject();
sideParent.name="Side"+side;
}
}
if(i>cubeSize.x-1) {
k++;
i=0;
}
GameObject plane=new GameObject();
plane.transform.parent=sideParent.transform;
MeshFilter meshFilter = (MeshFilter)plane.AddComponent(typeof(MeshFilter));
MeshRenderer meshRenderer = (MeshRenderer)plane.AddComponent(typeof(MeshRenderer));
Mesh mesh=new Mesh();
mesh.Clear();
mesh=maker.create(vertDensity,vertDensity,tileSize.x,tileSize.y);
meshFilter.mesh=mesh;
meshFilter.mesh.RecalculateNormals();
meshFilter.mesh.RecalculateBounds();
meshRenderer.material=planeMat;
switch (side) {
//Top Side
case 0:
xx=i*tileSize.x;
yy=((cubeSize.y*tileSize.y)/2f)+tileSize.y;
zz=k*tileSize.y;
dir=-Vector3.down;
break;
//Left Side
case 1:
xx=0;
yy=((cubeSize.y*tileSize.y)/2f)+(k*-tileSize.y);
zz=i*tileSize.x;
dir=Vector3.left;
break;
//Bottom Side
case 2:
xx=i*tileSize.x;
yy=((cubeSize.y*-tileSize.y)/2f)+tileSize.y;
zz=(k*tileSize.y)-tileSize.y;
dir=Vector3.down;
break;
//Right Side
case 3:
xx=(cubeSize.x*tileSize.x);
yy=(((cubeSize.y*tileSize.y)/2f)+tileSize.y)+(k*-tileSize.y);
zz=i*tileSize.x;
dir=-Vector3.left;
break;
//Front Side
case 4:
xx=i*tileSize.y;
yy=((cubeSize.y*tileSize.y)/2f)+(k*-tileSize.y);
zz=(cubeSize.y*tileSize.y)-tileSize.y;
dir=Vector3.forward;
break;
//Back Side
case 5:
xx=i*tileSize.y;
yy=(((cubeSize.y*tileSize.y)/2f)+tileSize.y)+(k*-tileSize.y);
zz=-tileSize.y;
dir=-Vector3.forward;
break;
}
plane.transform.position=new Vector3(xx,yy,zz);
plane.transform.up=dir;
plane.name=i+","+k+","+side+","+index;
meshList[index]=plane;
i++;
//index++;
}
}
CreatePlane (mesh creation script):
using UnityEngine;
using UnityEditor;
using System.Collections;
using System;
public class CreatePlane : MonoBehaviour
{
public enum Orientation
{
Horizontal,
Vertical
}
public enum AnchorPoint
{
TopLeft,
TopHalf,
TopRight,
RightHalf,
BottomRight,
BottomHalf,
BottomLeft,
LeftHalf,
Center
}
//public int widthSegments = 1;
//public int lengthSegments = 1;
//public float width = 1.0f;
//public float length = 1.0f;
public Orientation orientation = Orientation.Horizontal;
public AnchorPoint anchor = AnchorPoint.Center;
public bool addCollider = false;
public bool createAtOrigin = true;
public bool twoSided = false;
public string optionalName;
/*void OnWizardUpdate()
{
widthSegments = Mathf.Clamp(widthSegments, 1, 254);
lengthSegments = Mathf.Clamp(lengthSegments, 1, 254);
}*/
public Mesh create(int widthSegments, int lengthSegments, float width, float length)
{
Vector2 anchorOffset;
string anchorId;
switch (anchor)
{
case AnchorPoint.Center:
default:
anchorOffset = new Vector2(-width/2.0f,length/2.0f);
anchorId = "TL";
break;
}
/*GameObject plane = new GameObject();
if (!string.IsNullOrEmpty(optionalName))
plane.name = optionalName;
else
plane.name = "Plane";
plane.transform.position = Vector3.zero;
MeshFilter meshFilter = (MeshFilter)plane.AddComponent(typeof(MeshFilter));
plane.AddComponent(typeof(MeshRenderer));
string planeAssetName = plane.name + widthSegments + "x" + lengthSegments + "W" + width + "L" + length + anchorId + ".asset";*/
Mesh m = new Mesh();
//m.name = plane.name;
int hCount2 = widthSegments+1;
int vCount2 = lengthSegments+1;
int numTriangles = widthSegments * lengthSegments * 6;
numTriangles *= 2;
int numVertices = hCount2 * vCount2;
Vector3[] vertices = new Vector3[numVertices];
Vector2[] uvs = new Vector2[numVertices];
int[] triangles = new int[numTriangles];
int index = 0;
float uvFactorX = 1.0f/widthSegments;
float uvFactorY = 1.0f/lengthSegments;
float scaleX = width/widthSegments;
float scaleY = length/lengthSegments;
for (float y = 0.0f; y < vCount2; y++)
{
for (float x = 0.0f; x < hCount2; x++)
{
vertices[index] = new Vector3(x*scaleX - width/2f - anchorOffset.x, 0.0f, y*scaleY - length/2f - anchorOffset.y);
uvs[index++] = new Vector2(x*uvFactorX, y*uvFactorY);
}
}
index = 0;
for (int y = 0; y < lengthSegments; y++)
{
for (int x = 0; x < widthSegments; x++)
{
triangles[index] = (y * hCount2) + x;
triangles[index+1] = ((y+1) * hCount2) + x;
triangles[index+2] = (y * hCount2) + x + 1;
triangles[index+3] = ((y+1) * hCount2) + x;
triangles[index+4] = ((y+1) * hCount2) + x + 1;
triangles[index+5] = (y * hCount2) + x + 1;
index += 6;
}
if (twoSided) {
// Same tri vertices with order reversed, so normals point in the opposite direction
for (int x = 0; x < widthSegments; x++)
{
triangles[index] = (y * hCount2) + x;
triangles[index+1] = (y * hCount2) + x + 1;
triangles[index+2] = ((y+1) * hCount2) + x;
triangles[index+3] = ((y+1) * hCount2) + x;
triangles[index+4] = (y * hCount2) + x + 1;
triangles[index+5] = ((y+1) * hCount2) + x + 1;
index += 6;
}
}
}
m.vertices = vertices;
m.uv = uvs;
m.triangles = triangles;
//m.RecalculateNormals();
//meshFilter.sharedMesh = m;
//m.RecalculateBounds();
return m;
}
}
Spherify (self-explanatory):
using UnityEngine;
using System.Collections;
using System;
using System.Collections.Generic;
public class Spherify : MonoBehaviour {
// Use this for initialization
void Start () {
//MapToPlanet ();
}
// Update is called once per frame
void Update () {
}
public Mesh MapToPlanet(Mesh mesh,float radius, Vector3 offset){
Vector3[] vertices = mesh.vertices;
for (var i=0; i<vertices.Length; i++) {
vertices[i]=(offset+vertices[i]).normalized*radius;
}
mesh.vertices = vertices;
mesh.RecalculateNormals();
mesh.RecalculateBounds();
return mesh;
}
}
Answer by Komak57 · Jan 08, 2015 at 09:29 AM
The problem with your code is that normalized works by locking the direction from the origin, allowing you to move the vertex away from said point. This given point is what's know as the pivot. This pivot coordinate MUST be the same point used for all vertices, and should be deducted from the vertex coordinate, so that the normalized vertex ray will expand outwards by the given radius.
Next, having so many 64x64 planes will make for an extremely smooth sphere in most cases, you're talking about rendering up to 33,554,432 triangles. The render time for which will be flat out rediculous. What you'll want to look for, instead, is a basic plane with just enough vertices to keep the sphere from looking too blocky, and then applying various LOD methods to it to get better definition where the camera needs it. These methods include, but are not limited to, geo clipmapping, geo mipmapping, quad tree, octo tree, dynamic subdivision, etc.
Your answer
Follow this Question
Related Questions
Random Room Generator 1 Answer
Code to randomly generate a mesh? 0 Answers
how can i make randomly generated worlds with collectable resources 0 Answers
Infinite loop when I try to generate randomly a 2D dungeon.. 3 Answers
How can I make a Terraria Style game from sprites without using an enormous amount of gameobjects? 2 Answers