Reputation: 19
I'm working on a unity project involving deformable terrain based on marching-cubes. It works by generating a map of density over the 3-dimensional coordinates of a terrain chunk and using that data to create a mesh representing the surface of the terrain. It has been working, however the process is very slow. I'm attempting to introduce multithreading to improve performance, but I've run into a problem that's left me scratching my head.
When I run CreateMeshData() and try to pass my density map terrainMap into the MarchCubeJob struct, it recognizes it as a reference type, not a value type. I've seemed to whittle down the errors to this one, but I've tried to introduce the data in every way I know how and I'm stumped. I thought passing a reference like this was supposed to create a copy of the data disconnected from the reference, but my understanding must be flawed. My goal is to pass each marchingcube cube into a job and have them run concurrently.
I'm brand new to multithreading, so I've probably made some newbie mistakes here and I'd appreciate if someone would help me out with a second look. Cheers!
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using Unity.Burst;
public class Chunk
{
List<Vector3> vertices = new List<Vector3>();
List<int> triangles = new List<int>();
public GameObject chunkObject;
MeshFilter meshFilter;
MeshCollider meshCollider;
MeshRenderer meshRenderer;
Vector3Int chunkPosition;
public float[,,] terrainMap;
// Job system
NativeList<Vector3> marchVerts;
NativeList<Vector3> marchTris;
MarchCubeJob instanceMarchCube;
JobHandle instanceJobHandle;
int width { get { return Terrain_Data.chunkWidth;}}
int height { get { return Terrain_Data.chunkHeight;}}
static float terrainSurface { get { return Terrain_Data.terrainSurface;}}
public Chunk (Vector3Int _position){ // Constructor
chunkObject = new GameObject();
chunkObject.name = string.Format("Chunk x{0}, y{1}, z{2}", _position.x, _position.y, _position.z);
chunkPosition = _position;
chunkObject.transform.position = chunkPosition;
meshRenderer = chunkObject.AddComponent<MeshRenderer>();
meshFilter = chunkObject.AddComponent<MeshFilter>();
meshCollider = chunkObject.AddComponent<MeshCollider>();
chunkObject.transform.tag = "Terrain";
terrainMap = new float[width + 1, height + 1, width + 1]; // Weight of each point
meshRenderer.material = Resources.Load<Material>("Materials/Terrain");
// Generate chunk
PopulateTerrainMap();
CreateMeshData();
}
void PopulateTerrainMap(){
...
}
void CreateMeshData(){
ClearMeshData();
vertices = new List<Vector3>();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
for (int z = 0; z < width; z++) {
Debug.Log(x + ", " + y + ", " + z + ", begin");
Vector3Int position = new Vector3Int(x, y, z);
// Set up memory pointers
NativeList<Vector3> marchVerts = new NativeList<Vector3>(Allocator.TempJob);
NativeList<int> marchTris = new NativeList<int>(Allocator.TempJob);
NativeList<float> mapSample = new NativeList<float>(Allocator.TempJob);
// Split marchcube into jobs by cube
instanceMarchCube = new MarchCubeJob(){
position = position,
marchVerts = marchVerts,
marchTris = marchTris,
mapSample = terrainMap
};
// Run job for each cube in a chunk
instanceJobHandle = instanceMarchCube.Schedule();
instanceJobHandle.Complete();
// Copy data from job to mesh data
//instanceMarchCube.marchVerts.CopyTo(vertices);
vertices.AddRange(marchVerts);
triangles.AddRange(marchTris);
// Dispose of memory pointers
marchVerts.Dispose();
marchTris.Dispose();
mapSample.Dispose();
Debug.Log(x + ", " + y + ", " + z + ", end");
}
}
}
BuildMesh();
}
public void PlaceTerrain (Vector3 pos, int radius, float speed){
...
CreateMeshData();
}
public void RemoveTerrain (Vector3 pos, int radius, float speed){
...
CreateMeshData();
}
void ClearMeshData(){
vertices.Clear();
triangles.Clear();
}
void BuildMesh(){
Mesh mesh = new Mesh();
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
meshFilter.mesh = mesh;
meshCollider.sharedMesh = mesh;
}
private void OnDestroy(){
marchVerts.Dispose();
marchTris.Dispose();
}
}
// Build a cube as a job
[BurstCompile]
public struct MarchCubeJob: IJob{
static float terrainSurface { get { return Terrain_Data.terrainSurface;}}
public Vector3Int position;
public NativeList<Vector3> marchVerts;
public NativeList<int> marchTris;
public float[,,] mapSample;
public void Execute(){
//Sample terrain values at each corner of cube
float[] cube = new float[8];
for (int i = 0; i < 8; i++){
cube[i] = SampleTerrain(position + Terrain_Data.CornerTable[i]);
}
int configIndex = GetCubeConfiguration(cube);
// If done (-1 means there are no more vertices)
if (configIndex == 0 || configIndex == 255){
return;
}
int edgeIndex = 0;
for (int i = 0; i < 5; i++){ // Triangles
for (int p = 0; p < 3; p++){ // Tri Vertices
int indice = Terrain_Data.TriangleTable[configIndex, edgeIndex];
if (indice == -1){
return;
}
// Get 2 points of edge
Vector3 vert1 = position + Terrain_Data.CornerTable[Terrain_Data.EdgeIndexes[indice, 0]];
Vector3 vert2 = position + Terrain_Data.CornerTable[Terrain_Data.EdgeIndexes[indice, 1]];
Vector3 vertPosition;
// Smooth terrain
// Sample terrain values at either end of current edge
float vert1Sample = cube[Terrain_Data.EdgeIndexes[indice, 0]];
float vert2Sample = cube[Terrain_Data.EdgeIndexes[indice, 1]];
// Calculate difference between terrain values
float difference = vert2Sample - vert1Sample;
if (difference == 0){
difference = terrainSurface;
}
else{
difference = (terrainSurface - vert1Sample) / difference;
}
vertPosition = vert1 + ((vert2 - vert1) * difference);
marchVerts.Add(vertPosition);
marchTris.Add(marchVerts.Length - 1);
edgeIndex++;
}
}
}
static int GetCubeConfiguration(float[] cube){
int configurationIndex = 0;
for (int i = 0; i < 8; i++){
if (cube[i] > terrainSurface){
configurationIndex |= 1 << i;
}
}
return configurationIndex;
}
public float SampleTerrain(Vector3Int point){
return mapSample[point.x, point.y, point.z];
}
}
Upvotes: 0
Views: 1325