Why does the array I pass to my multithreading job struct act as a reference type? - c#

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

Related

Unity Trying to make a chess game but error messages really annoys me

Codes below:
using System;
using UnityEngine;
using UnityEngine.Serialization;
public class Chessboard : MonoBehaviour
{
[Header("Art")]
[SerializeField] private Material tileMaterial;
//Logic
private const int TILE_COUNT_X = 8;
private const int TILE_COUNT_Y = 8;
private GameObject[,] tiles;
private Camera currentCamera;
private Vector2Int currentHover = -Vector2Int.one;
private void Awake()
{
GenerateAllTiles(1, TILE_COUNT_X, TILE_COUNT_Y);
}
private void Update()
{
if (!currentCamera)
{
currentCamera = Camera.current;
return;
}
RaycastHit info;
Ray ray = currentCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out info, 100, LayerMask.GetMask("Tile")))
{
// get the indexes of Tile i've hit
Vector2Int hitPosition = LookupTileIndex(info.transform.gameObject);
//If we are hovering a tile after not hovering any
if (currentHover == -Vector2Int.one)
{
tiles[hitPosition.x, hitPosition.y].layer = LayerMask.NameToLayer("Hover");
currentHover = hitPosition;
}
//If we were already hovering a tile, change the previous one
else if (currentHover != hitPosition)
{
tiles[currentHover.x, currentHover.y].layer = LayerMask.NameToLayer("Tile");
currentHover = hitPosition;
tiles[hitPosition.x, hitPosition.y].layer = LayerMask.NameToLayer("Hover");
}
}
else
{
if (currentHover != -Vector2Int.one)
{
tiles[currentHover.x, currentHover.y].layer = LayerMask.NameToLayer("Tile");
currentHover = -Vector2Int.one;
}
}
}
//Generate the board (useful)
private void GenerateAllTiles(float tileSize, int tileCountX, int tileCountY)
{
tiles = new GameObject[tileCountX, tileCountY];
for (int x = 0; x < tileCountX; x++)
for (int y = 0; y < tileCountY; y++)
tiles[x, y] = generate1tile(tileSize, x, y);
}
private GameObject generate1tile(float tileSize, int x, int y)
{
GameObject tileObject = new GameObject(string.Format("Tile X:{0}, Y:{1}", x, y));
tileObject.transform.parent = transform;
Mesh mesh = new Mesh();
tileObject.AddComponent<MeshFilter>().mesh = mesh;
tileObject.AddComponent<MeshRenderer>().material = tileMaterial;
Vector3[] vertices = new Vector3[4];
vertices[0] = new Vector3(x * tileSize, 0, y * tileSize);
vertices[1] = new Vector3(x * tileSize, 0, (y + 1) * tileSize);
vertices[2] = new Vector3((x + 1) * tileSize, 0, y * tileSize);
vertices[3] = new Vector3((x + 1) * tileSize, 0, (y + 1) * tileSize);
int[] tris = new int[]{0, 2, 1, 1, 2, 3};
mesh.vertices = vertices;
mesh.triangles = tris;
mesh.RecalculateNormals();
tileObject.layer = LayerMask.NameToLayer("Tile");
tileObject.AddComponent<BoxCollider>();
return tileObject;
}
private Vector2Int LookupTileIndex(GameObject hitInfo)
{
for(int x = 0; x < TILE_COUNT_X; x++)
for(int y = 0; y < TILE_COUNT_Y; y++)
if(tiles[x, y] == hitInfo)
return new Vector2Int(x, y);
return new Vector2Int(-1, -1); //Invalid
}
}
Error Message:
A game object can only be in one layer. The layer needs to be in the range [0...31]
UnityEngine.StackTraceUtility:ExtractStackTrace ()
Chessboard:generate1tile (single,int,int) (at Assets/scripts/Chessboard.cs:93)
Chessboard:GenerateAllTiles (single,int,int) (at Assets/scripts/Chessboard.cs:68)
Chessboard:Awake () (at Assets/scripts/Chessboard.cs:20)
I just get into Unity, so I might be missed some parts of it.
I tired using AI to config my problem, but it doesn't work. I am expecting i put my mouse on a tile, the tile change its color.
Here is the link to the tutorial I watched: https://www.youtube.com/watch?v=FtGy7J8XD90&list=PLmcbjnHce7SeAUFouc3X9zqXxiPbCz8Zp&index=2&ab_channel=Epitome

Issues with Steering Behavior Seperation

The problem
I am trying to procedurally generate dungeon rooms with random X, Y sizes inside of a radius (r). However, even after I validate that the starting grid (origin of the "room") is not in the same position as other origins after running the separation function there are rooms still building inside of each other.
Solutions I have tried
I tried using math to calculate an optimal radius that will be able to fit the average of all the room sizes * amount of rooms. However, the separation should hypothetically work with any radius (though I want to keep them relatively close in order to keep hallways short).
Code
All my code is based on one tile. This means that all calculations are using one tile, and will remain one tile until the very end, then I scale them up.
private void GenerateRooms(int amount)
{
// init sizes
Vector2[] room_sizes = new Vector2[amount];
for (int i = 0; i < amount; i++)
{
room_sizes[i] = new Vector2(Random.Range(minimum_room_height, maximum_room_height), Random.Range(minimum_room_width, maximum_room_width));
}
float biggest_room = calculations.CalculateBiggest(room_sizes);
Vector2[] room_points = new Vector2[amount];
Vector2[] used_points = new Vector2[amount];
float radius = calculations.CalculateAverage(room_sizes) * amount;
for (int i = 0; i < amount; i++)
{
do {
Vector2 test_point = new Vector2(Random.Range(-radius, radius), Random.Range(-radius, radius));
foreach (Vector2 point in used_points) {
if (test_point == point) {
continue;
} else {
room_points[i] = test_point;
used_points[i] = test_point;
break;
}
}
} while (Vector2.Distance(Vector2.zero, room_points[i]) < radius);
}
for (int i = 0; i < amount; i++)
{
//Vector2 origin = room_points[i];
Vector3 position = calculations.computeSeperate(room_points, room_points[i], biggest_room);
//position = new Vector3(position.x + origin.x, position.y + origin.y, 0);
Vector3Int location = tile_map.WorldToCell(position);
tile_map.SetTile(location, tile);
calculations.scaleUpRooms(position, room_sizes[i].x, room_sizes[i].y, tile_map, tile);
}
}
The above is code for calling all the functions and validating the points. Here are the important functions (calculation functions):
public Vector2 computeSeperate(Vector2[] point_array, Vector2 target_point, float minimum_distance)
{
int neighbor_count = 0;
for (int i = 0; i < point_array.Length; i++)
{
if (point_array[i] != target_point)
{
if (Vector2.Distance(target_point, point_array[i]) < minimum_distance * 2)
{
target_point.x += point_array[i].x - target_point.x;
target_point.y += point_array[i].y - target_point.y;
neighbor_count++;
}
}
}
if (neighbor_count == 0)
{
return target_point;
} else
{
target_point.x /= neighbor_count;
target_point.y /= neighbor_count;
target_point.x *= -1;
target_point.y *= -1;
target_point.Normalize();
return target_point;
}
}
public void scaleUpRooms(Vector2 base_point, float scale_x, float scale_y, Tilemap tile_map, Tile tile) // ex: 5x5
{
List<Vector2> Calculate(Vector2 size)
{
List<Vector2> results = new List<Vector2>();
for (int i = 0; i < size.y; i++)
for (int o = 0; o < size.x; o++)
results.Add(new Vector2(o, i) + (new Vector2(size.x % 2 != 0 ? .5f : 1, size.y % 2 != 0 ? .5f : 1) - (size / 2)));
string st = "";
for (int i = 0; i < results.Count; i++)
st += "\n" + results[i].ToString();
return results;
}
Vector2 desired_scale = new Vector2(scale_x, scale_y);
List<Vector2> Offsets = Calculate(desired_scale);
for (int i = 0; i < Offsets.Count; i++)
{
Vector3 position = base_point + Offsets[i];
Vector3Int location = tile_map.WorldToCell(position);
tile_map.SetTile(location, tile);
}
}

Unity randomiser generating results in a non-random gradient

I'm using code from the tutorial series "make a game" by Sebastian Lague and so far everything has been easily adapted to the new versions of unity. However on Episode 9, I have become unstuck. I have adapted the code for the map generator to get it to compile in my version of unity, 2019.3.11f1. However, the random map generation seems to bias towards lowest coordinated of the map.
My Gen results:
What it should look like:
[
This code is the map generator script it is placed on an empty in the world and adds cubes as obstacles, provided with some prefabs.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MapGenerator : MonoBehaviour
{
public Transform tilePrefab;
public Vector2 mapSize;
public int seed = 10;
public Transform obstaclePrefab;
public int obstacleCount = 10;
[Range(0, 1)]
public float outlinePercent; // Cotrols scale of tiles
List<Coord> allTileCoords;
Queue<Coord> shuffledTileCoords;
private void Start()
{
GenerateMap();
}
public void GenerateMap()
{
allTileCoords = new List<Coord>();
for (int x = 0; x < mapSize.x; x++)
{
for (int y = 0; y < mapSize.y; y++)
{
allTileCoords.Add(new Coord(x, y)); //iterate through mapSize adding tiles
}
}
shuffledTileCoords = new Queue<Coord>(Utility.ShuffleArray(allTileCoords.ToArray(), seed)); //shuffled array of tiles coords
string holderName = "Generated Map"; //Added for the editor script
if (GameObject.Find(holderName)) //Added for the editor script
{ //Added for the editor script
DestroyImmediate(GameObject.Find(holderName)); //Added for the editor script
} //Added for the editor script
Transform mapHolder = new GameObject(holderName).transform; //This is only neccessary because of the editor script
mapHolder.parent = transform; //This is only neccessary because of the editor script
for (int x = 0; x < mapSize.x; x++)
{
for (int y = 0; y < mapSize.y; y++)
{
Vector3 tilePosition = relativeToSpacial(x, y); //converts grid x,y data to real spatial coords
Transform newTile = Instantiate(tilePrefab, tilePosition, Quaternion.Euler(Vector3.right * 90)) as Transform; //instantiates tile in loctaion from rel to spatial func with 90 rotation
newTile.localScale = Vector3.one * (1 - outlinePercent); //scales down tiles to leave a outline
newTile.parent = mapHolder;
}
}
for (int i = 0; i < obstacleCount; i++)
{
Coord randomCoord = GetRandomCoord();
Vector3 obstaclePosition = relativeToSpacial(randomCoord.x, randomCoord.y);
Transform newObstacle = Instantiate(obstaclePrefab, obstaclePosition + Vector3.up * .5f, Quaternion.identity) as Transform;
newObstacle.parent = mapHolder;
}
}
Vector3 relativeToSpacial(int x, int y)
{
return new Vector3(-mapSize.x / 2 + .5f + x, 0, -mapSize.y / 2 + .5f + y);
}
public Coord GetRandomCoord()
{
Coord randomCoord = shuffledTileCoords.Dequeue();
shuffledTileCoords.Enqueue(randomCoord);
//print("|| " + randomCoord.x + " || " + randomCoord.y + " ||");
return randomCoord;
}
public struct Coord
{
public int x;
public int y;
public Coord(int _x, int _y)
{
x = _x;
y = _y;
}
}
}
This is the code from Utility.ShuffleArray which is my custom function for shuffling arrays.
using System.Collections;
using System.Collections.Generic;
public static class Utility
{
public static T[] ShuffleArray<T>(T[] array, int seed)
{
System.Random prng = new System.Random(seed);
for (int i =0; i < array.Length -1; i++)
{
int randomIndex = prng.Next(i, array.Length);
T tempItem = array[randomIndex];
array[randomIndex] = array[i];
}
return array;
}
}
Any help appreciated.
int randomIndex = prng.Next(i, array.Length);
You are incrementing the clamp on your rng in this line in your for loop, so it becomes increasingly bias to the end of the array, also I added code to do a swap of positions rather than duplicate of position try something like this
int randomIndex = prng.Next(0, array.Length);
T tempItem = array[randomIndex];
T tempItemTwo = array[i];
array[randomIndex] = array[i];
array[i] = tempItemTWo;
if that doesnt work also try:
int randomIndex = prng.Next(i, array.Length);
T tempItem = array[randomIndex];
T tempItemTwo = array[i];
array[randomIndex] = array[i];
array[i] = tempItemTWo;

Place 3 Objects Randomly In 3 Specified Positions in Unity3D

How do I place 3 gameobjects in random positions range without any of the object having same position as the other 2? How do I achieve this?
So far..
public GameObject[] sprites;
int[] Position = new int[3] { 0, 5, -5 };
int resultSprite1;
int resultSprite2;
int resultSprite3;
int y;
int z;
Vector3 pos;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.R))
{
resultSprite1 = Position[Random.Range(0, Position.Length)];
y = 0;
z = 0;
pos = new Vector3(resultSprite1, y, z);
sprites[0].transform.position = pos;
resultSprite2 = Position[Random.Range(0, Position.Length)];
y = 0;
z = 0;
pos = new Vector3(resultSprite2, y, z);
sprites[1].transform.position = pos;
resultSprite3 = Position[Random.Range(0, Position.Length)];
y = 0;
z = 0;
pos = new Vector3(resultSprite3, y, z);
sprites[2].transform.position = pos;
}
}
How to achieve this so that no two objects share same positions. All of them should be independent of one another.
Thanks.
List<int> usedNumbers = new List<int> ();
void Update ()
{
if (Input.GetKeyDown (KeyCode.R))
{
//Since it works on update. You should clear the last list before creating a new one.
usedNumbers.Clear();
for (int i = 0; i < Position.Length; i++)
{
do
{
number = Random.Range (0, Position.Length);
} while (usedNumbers.Contains (number));
//You have your unique number here.
sprites[i].transform.position = new Vector3 (Position[number], 0f, 0f);
//Do not forget to store your number in usedNumbersList.
usedNumbers.Add (number);
}
}
}

Size issues when creating a procedural mesh generator in Unity

I've been working on building a procedural generator for Unity, that takes in noise and uses it to build a height map.
So far, everything seems to work as long as I limit the size of the mesh to around 250x250. If I attempt to make a larger mesh, the script won't calculate it.
The puzzling thing is that I get no memory errors or anything of that nature. I've implemented a Regenerate button that allows me to generate a new mesh in Unity and as long as I remain in the range of 250x250 or less, it works fine. If I pick a larger value, the mesh simply remains unchanged.
How I calculate the Mesh:
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;
[ExecuteInEditMode]
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(Noise))]
public class CustomTerrain : MonoBehaviour {
//Note: sizeX*sizeZ determines mesh size.
public int sizeX = 4; //These values are public so they can be
public int sizeZ = 4; //modified in the Unity Editor.
public float noiseSize = 1.0f;
public float cellSize = 1.0f;
private string mPath = "Assets/Generated/";
Noise noise;
void Start() {
noise = GetComponent<Noise>();
this.LoadMesh();
if (!GetComponent<MeshFilter>().sharedMesh) {
this.Regenerate();
}
}
public void LoadMesh() {
Mesh mesh = Instantiate(AssetDatabase.LoadMainAssetAtPath(mPath + gameObject.name + ".asset") as Mesh);
if (mesh) {
GetComponent<MeshFilter>().mesh = mesh;
}
recalculateMeshCollider(mesh);
}
Vector3[] GenVertices() {
float x, z;
int w = (sizeX+1);
int l = (sizeZ+1);
Vector3[] vertices = new Vector3[w*l];
for (int gx = 0; gx < w; gx++) {
for (int gz = 0; gz < l; gz++) {
x = gx*cellSize;
z = gz*cellSize;
float height = (noiseSize * noise.Get(x,z));
vertices[gx*l+gz] = new Vector3(x, height, z);
}
}
return vertices;
}
int[] GenTriangles() {
int vertciesPerTriangle = 3;
int trianglesPerCell = 2;
int numberCells = sizeX * sizeZ;
int[] triangles = new int[vertciesPerTriangle * trianglesPerCell * numberCells];
int tIndeX = 0;
for (int cX = 0; cX < sizeX; cX++) {
for (int cZ = 0; cZ < sizeZ; cZ++) {
int n = cX*(sizeZ+1)+cZ;
triangles[tIndeX] = n;
triangles[tIndeX+1] = n+1;
triangles[tIndeX+2] = n+sizeZ+2;
triangles[tIndeX+3] = n;
triangles[tIndeX+4] = n+sizeZ+2;
triangles[tIndeX+5] = n+sizeZ+1;
tIndeX +=6;
}
}
return triangles;
}
Vector2[] GenUVs() {
int w = (sizeX + 1);
int l = (sizeZ + 1);
Vector2[] uvs = new Vector2[w * l];
for (int uX = 0; uX < w; uX++) {
for (int uZ = 0; uZ < l; uZ++) {
uvs[uX*l+uZ] = new Vector2((float)uX/sizeX, (float)uZ/sizeZ);
}
}
return uvs;
}
}
My Regenerate function:
public void Regenerate() {
noise.Init();
Mesh mesh = GetComponent<MeshFilter>().sharedMesh;
if(!mesh) {
mesh = new Mesh();
GetComponent<MeshFilter>().sharedMesh = mesh;
}
mesh.vertices = GenVertices();
mesh.triangles = GenTriangles();
mesh.uv = GenUVs();
mesh.RecalculateNormals();
recalculateMeshCollider(mesh);
}
public void recalculateMeshCollider(Mesh mesh) {
if (GetComponent<MeshCollider>()) {
DestroyImmediate(GetComponent<MeshCollider>());
}
transform.gameObject.AddComponent<MeshCollider>();
transform.GetComponent<MeshCollider>().sharedMesh = mesh;
}
By "250x250" do you mean there's 62.400 triangles?
Unity has a vertex limit of 65535 count - just use more than one mesh, no trouble.

Categories