From 66193c740b0fd5650347cbe071b77c3d24e2b8b2 Mon Sep 17 00:00:00 2001 From: ccrock4t <15344554+ccrock4t@users.noreply.github.com> Date: Fri, 29 Sep 2023 03:30:15 -0400 Subject: [PATCH] World Automaton CPU --- .../Rendering/CPU (Mesh)/WorldAutomatonCPU.cs | 645 ++++++++++++++++++ .../CPU (Mesh)/WorldAutomatonCPU.cs.meta | 11 + Assets/Scripts/Automata/VoxelAutomaton.cs | 283 ++++++++ .../Scripts/Automata/VoxelAutomaton.cs.meta | 11 + Assets/Scripts/Automata/WorldAutomaton.cs | 188 +++++ .../Scripts/Automata/WorldAutomaton.cs.meta | 11 + 6 files changed, 1149 insertions(+) create mode 100644 Assets/Scripts/Automata/Rendering/CPU (Mesh)/WorldAutomatonCPU.cs create mode 100644 Assets/Scripts/Automata/Rendering/CPU (Mesh)/WorldAutomatonCPU.cs.meta create mode 100644 Assets/Scripts/Automata/VoxelAutomaton.cs create mode 100644 Assets/Scripts/Automata/VoxelAutomaton.cs.meta create mode 100644 Assets/Scripts/Automata/WorldAutomaton.cs create mode 100644 Assets/Scripts/Automata/WorldAutomaton.cs.meta diff --git a/Assets/Scripts/Automata/Rendering/CPU (Mesh)/WorldAutomatonCPU.cs b/Assets/Scripts/Automata/Rendering/CPU (Mesh)/WorldAutomatonCPU.cs new file mode 100644 index 0000000..b287d1f --- /dev/null +++ b/Assets/Scripts/Automata/Rendering/CPU (Mesh)/WorldAutomatonCPU.cs @@ -0,0 +1,645 @@ +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; +using UnityEngine.Rendering; +using static WorldAutomaton; +using static WorldAutomaton.Elemental; + +using WorldCellInfo = CellInfo; + +/// +/// Computes and renders the voxel automaton using multithreading on CPU +/// +public class WorldAutomatonCPU : WorldAutomaton +{ + + //CPU (mesh) + public NativeArray block_grid; // stores index for corner of each 2x2x2 Margolus block (unshifted blocks) + public NativeArray world_quad_vertices; + + public Material[] element_materials; + public static readonly int3[] voxel_vertices = new int3[] { + new(0, 0, 1), //p0 + new(1, 0, 1), //p1 + new(1, 0, 0), //p2 + new(0, 0, 0), //p3 + new(0, 1, 1), //p4 + new(1, 1, 1), //p5 + new(1, 1, 0), //p6 + new(0, 1, 0) // p7 + }; + public const int MAX_VERTICES_PER_MESH = 65532; // divisible by 4 (number of vertices in a quad) so vertices and tris wont get truncated + + public struct ArrayOfNativeList where T : unmanaged + { + + public NativeList element0_mesh_vertex_data_writer; + public NativeList element1_mesh_vertex_data_writer; + public NativeList element2_mesh_vertex_data_writer; + public NativeList element3_mesh_vertex_data_writer; + public NativeList element4_mesh_vertex_data_writer; + public NativeList element5_mesh_vertex_data_writer; + public int Length; + + + public NativeList this[int index] + { + get + { + switch (index) + { + case 0: + return this.element0_mesh_vertex_data_writer; + case 1: + return this.element1_mesh_vertex_data_writer; + case 2: + return this.element2_mesh_vertex_data_writer; + case 3: + return this.element3_mesh_vertex_data_writer; + case 4: + return this.element4_mesh_vertex_data_writer; + case 5: + return this.element5_mesh_vertex_data_writer; + default: + Debug.LogError("error"); + return this.element0_mesh_vertex_data_writer; + } + } + + set + { + switch (index) + { + case 0: + this.element0_mesh_vertex_data_writer = value; + break; + case 1: + this.element1_mesh_vertex_data_writer = value; + break; + case 2: + this.element2_mesh_vertex_data_writer = value; + break; + case 3: + this.element3_mesh_vertex_data_writer = value; + break; + case 4: + this.element4_mesh_vertex_data_writer = value; + break; + case 5: + this.element5_mesh_vertex_data_writer = value; + break; + default: + Debug.LogError("error"); + this.element0_mesh_vertex_data_writer = value; + + break; + } + } + } + } + + public struct ArrayOfNativeListWriter where T : unmanaged + { + + public NativeList.ParallelWriter element0_mesh_vertex_data_writer; + public NativeList.ParallelWriter element1_mesh_vertex_data_writer; + public NativeList.ParallelWriter element2_mesh_vertex_data_writer; + public NativeList.ParallelWriter element3_mesh_vertex_data_writer; + public NativeList.ParallelWriter element4_mesh_vertex_data_writer; + public NativeList.ParallelWriter element5_mesh_vertex_data_writer; + public int Length; + + + public NativeList.ParallelWriter this[int index] + { + get + { + NativeList.ParallelWriter writer; + switch (index) + { + case 0: + writer = this.element0_mesh_vertex_data_writer; + break; + case 1: + writer = this.element1_mesh_vertex_data_writer; + break; + case 2: + writer = this.element2_mesh_vertex_data_writer; + break; + case 3: + writer = this.element3_mesh_vertex_data_writer; + break; + case 4: + writer = this.element4_mesh_vertex_data_writer; + break; + case 5: + writer = this.element5_mesh_vertex_data_writer; + break; + default: + Debug.LogError("error"); + writer = this.element0_mesh_vertex_data_writer; + break; + } + return writer; + } + + set + { + switch (index) + { + case 0: + this.element0_mesh_vertex_data_writer = value; + break; + case 1: + this.element1_mesh_vertex_data_writer = value; + break; + case 2: + this.element2_mesh_vertex_data_writer = value; + break; + case 3: + this.element3_mesh_vertex_data_writer = value; + break; + case 4: + this.element4_mesh_vertex_data_writer = value; + break; + case 5: + this.element5_mesh_vertex_data_writer = value; + break; + default: + Debug.LogError("error"); + this.element0_mesh_vertex_data_writer = value; + + break; + } + } + } + } + + public struct ArrayOfNativeArray where T : struct + { + + public NativeArray element0_mesh_vertex_data_writer; + public NativeArray element1_mesh_vertex_data_writer; + public NativeArray element2_mesh_vertex_data_writer; + public NativeArray element3_mesh_vertex_data_writer; + public NativeArray element4_mesh_vertex_data_writer; + public NativeArray element5_mesh_vertex_data_writer; + public int Length; + + + public NativeArray this[int index] + { + get + { + switch (index) + { + case 0: + return this.element0_mesh_vertex_data_writer; + case 1: + return this.element1_mesh_vertex_data_writer; + case 2: + return this.element2_mesh_vertex_data_writer; + case 3: + return this.element3_mesh_vertex_data_writer; + case 4: + return this.element4_mesh_vertex_data_writer; + case 5: + return this.element5_mesh_vertex_data_writer; + default: + Debug.LogError("error"); + return this.element0_mesh_vertex_data_writer; + } + } + + set + { + switch (index) + { + case 0: + this.element0_mesh_vertex_data_writer = value; + break; + case 1: + this.element1_mesh_vertex_data_writer = value; + break; + case 2: + this.element2_mesh_vertex_data_writer = value; + break; + case 3: + this.element3_mesh_vertex_data_writer = value; + break; + case 4: + this.element4_mesh_vertex_data_writer = value; + break; + case 5: + this.element5_mesh_vertex_data_writer = value; + break; + default: + Debug.LogError("error"); + this.element0_mesh_vertex_data_writer = value; + + break; + } + } + } + } + + + public Dictionary> meshGameObjects; // gameobjects for the mesh objects + public Dictionary> meshFilters; // filter component for the mesh objects + public Mesh[] mesh_array; + public List mesh_list; // a list of mesh data structures / objects that can be re-used each frame + public Dictionary> meshRenderers; + public Dictionary> meshColliders; + + public NativeArray neighbor_offsets_int3; // so we dont have to keep making new objects, store coordinates here + + ArrayOfNativeList vertex_index_lists; + ArrayOfNativeListWriter vertex_index_lists_writer; + + public override void Setup(WorldCellInfo[] cell_grid, int3[] block_grid) + { + + this.transform.parent = null; + // initialize + this.cell_grid = new NativeArray(cell_grid, + Allocator.Persistent); + + this.world_quad_vertices = new NativeArray(this.automaton_size * 6, // 6 faces per voxel + Allocator.Persistent, + NativeArrayOptions.UninitializedMemory); + + this.block_grid = new NativeArray(block_grid, + Allocator.Persistent); + + // load resources + this.element_materials = new Material[Elemental.number_of_elements]; + for(int i=0; i < this.element_materials.Length; i++) + { + string element_name = System.Enum.GetName(typeof(Elemental.Element), i); + this.element_materials[i] = (Material)Resources.Load(element_name); + } + + + // Set up mesh and job stuff + this.meshGameObjects = new(); + this.meshFilters = new(); + this.meshRenderers = new(); + this.meshColliders = new(); + for(int i=0; i < Elemental.number_of_elements; i++) { + Element element = (Element)i; + this.meshGameObjects.Add(element, new List()); + this.meshRenderers.Add(element, new List()); + this.meshFilters.Add(element, new List()); + this.meshColliders.Add(element, new List()); + } + + + if (GlobalConfig.voxel_mesh_smoothing_method == GlobalConfig.SmoothingMethod.MarchingCubes) + { + BuildVoxelWorldMeshMarchingCubes buildjob = new() + { + vertices = this.world_quad_vertices + }; + // precompute voxel vertices + JobHandle buildJobHandle = buildjob.Schedule(this.cell_grid.Length, 256); + buildJobHandle.Complete(); + } + else + { + BuildVoxelWorldMesh buildjob = new() + { + automaton_dimensions = this.automaton_dimensions, + vertices = this.world_quad_vertices + }; + // precompute voxel vertices + JobHandle buildJobHandle = buildjob.Schedule(this.cell_grid.Length, 256); + buildJobHandle.Complete(); + } + + + + + // precompute local Moore neighborhood index offsets for later use (from [-1,-1,-1] to [1,1,1]) + neighbor_offsets_int3 = new NativeArray(27, Allocator.Persistent); + int key = 0; + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + for (int z = -1; z <= 1; z++) + { + neighbor_offsets_int3[key] = new int3(x, y, z); + key++; + } + } + } + + this.mesh_list = new(); + this.mesh_array = new Mesh[0]; + + this.setup = true; + } + + + + + + + + + public override void RunMouseButtonBehaviors(Element state, int brush_size) + { + float distance = 2.0f; + Vector3 point = Camera.main.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, distance)); + int3 index = new int3(Mathf.RoundToInt(point.x), Mathf.RoundToInt(point.y), Mathf.RoundToInt(point.z)); + + //check all downward neighbors + for (int nx = index.x - brush_size; nx <= index.x + brush_size; nx++) + { + for (int ny = index.y - brush_size; ny <= index.y + brush_size; ny++) + { + for (int nz = index.z - brush_size; nz <= index.z + brush_size; nz++) + { + if (VoxelUtils.IsOutOfBounds(nx, ny, nz, GlobalConfig.world_automaton.automaton_dimensions)) continue; + VoxelAutomaton.SetCellNextState(this.cell_grid, GlobalConfig.world_automaton.automaton_dimensions, this.frame, nx, ny, nz, state); + int i = VoxelUtils.Index_FlatFromint3(nx, ny, nz, GlobalConfig.world_automaton.automaton_dimensions); + WorldCellInfo value = this.cell_grid[i]; + this.cell_grid[i] = value; + } + + } + } + } + + + private void ProcessUserInput() + { + if (Input.GetMouseButton(0) || Input.GetMouseButton(1)) + { + // RunMouseButtonBehaviors(CanvasNav.GetButtonCellState(), this.brush_size); + } + } + + public void CalculateNextGridState() + { + /// + /// Automaton + /// + + // calculate next grid state + ParallelCalculateNextWorldState job = new() + { + cell_grid = this.cell_grid, + block_grid = this.block_grid, + frame_mod2 = (byte)(this.frame % 2), + neighbor_offsets_int3 = this.neighbor_offsets_int3, + frame = this.frame, + automaton_dimensions = this.automaton_dimensions + }; + JobHandle jobHandle = job.Schedule(block_grid.Length, 256); + jobHandle.Complete(); + } + + /// + /// Render the next state of the automata + /// + public void RenderNextGridState() + { + // TODO below - filter out the vertices for all elements at once + vertex_index_lists = new(); + vertex_index_lists_writer = new(); + for (int i = 0; i < Elemental.number_of_elements; i++) + { + NativeList vertex_index_list = new(this.world_quad_vertices.Length, Allocator.TempJob); + vertex_index_lists[i] = vertex_index_list; + vertex_index_lists_writer[i] = vertex_index_list.AsParallelWriter(); + } + + // filter out the visible vertex indices for each element, so we don't have to create tons of meshes that only hold invisible vertices + FilterVoxelVertexIndices jobVertexIndexFilter = new() + { + cell_grid = this.cell_grid, + vertex_index_lists = vertex_index_lists_writer, + unfiltered_quad_vertices = this.world_quad_vertices, + automaton_dimensions = this.automaton_dimensions + }; + + JobHandle jobHandle = jobVertexIndexFilter.Schedule(this.world_quad_vertices.Length, 256); + jobHandle.Complete(); + + + // for each element, determine if we need more mesh objects to render everything + int total_number_of_meshes = 0; + int element_number_of_meshes; + int[] meshes_per_element = new int[Elemental.number_of_elements]; + for (int i = 0; i < number_of_elements; i++) + { + if (vertex_index_lists[i].Length != 0) + { + element_number_of_meshes = Mathf.CeilToInt(vertex_index_lists[i].Length * 4.0f / MAX_VERTICES_PER_MESH); + } + else + { + element_number_of_meshes = 0; + } + meshes_per_element[i] = element_number_of_meshes; + total_number_of_meshes += element_number_of_meshes; + } + + + // Allocate mesh data + Mesh.MeshDataArray job_mesh_data = Mesh.AllocateWritableMeshData(total_number_of_meshes); + NativeArray mesh_index_to_element_idx_and_element = new NativeArray(total_number_of_meshes, Allocator.TempJob); + Mesh.MeshData data; + int z = 0; + // for each element, create more mesh objects if needed. Also set the number of vertices to be allocated for each MeshDataArray. + for (int i = 0; i < number_of_elements; i++) + { + Element element = (Element)i; + element_number_of_meshes = meshes_per_element[i]; + + // create new gameobject if needed + while (element_number_of_meshes > this.meshGameObjects[element].Count) + { + GameObject meshGO = new GameObject(element.ToString() + " " + this.meshGameObjects[element].Count); + this.meshFilters[element].Add(meshGO.AddComponent()); + this.meshRenderers[element].Add(meshGO.AddComponent()); + this.meshGameObjects[element].Add(meshGO); + this.meshRenderers[element][this.meshGameObjects[element].Count - 1].material = element_materials[(int)element]; + } + + int remaining_vertices = vertex_index_lists[i].Length * 4; // 4 vertex per quad + int vertices_to_allocate; + int tris_to_allocate; + + for (int j = 0; j < element_number_of_meshes; j++) + { + // set parameters for the allocated parallel writable mesh data + mesh_index_to_element_idx_and_element[z] = new int2(j, (int)element); + vertices_to_allocate = (remaining_vertices > WorldAutomatonCPU.MAX_VERTICES_PER_MESH) ? WorldAutomatonCPU.MAX_VERTICES_PER_MESH : remaining_vertices; + tris_to_allocate = (vertices_to_allocate / 4) * 6; + + data = job_mesh_data[z]; + data.SetIndexBufferParams(tris_to_allocate, IndexFormat.UInt32); + data.SetVertexBufferParams(vertices_to_allocate, + new VertexAttributeDescriptor(VertexAttribute.Position)); + //new VertexAttributeDescriptor(VertexAttribute.Normal, stream: 1)); + + z++; + remaining_vertices -= vertices_to_allocate; + } + } + + // Now create or remove actual mesh objects + while (this.mesh_list.Count < z) + { + Mesh mesh = new Mesh(); + this.mesh_list.Add(mesh); + } + + // remove meshes if there are more meshes than needed + while (this.mesh_list.Count > z) + { + this.mesh_list.RemoveAt(0); + } + + + // Get mesh data in parallel + RenderNextGridParallel jobRender = new() + { + grid = this.cell_grid, + element_mesh_data = vertex_index_lists, + mesh_index_to_element = mesh_index_to_element_idx_and_element, + job_mesh_data = job_mesh_data + }; + + JobHandle jobRenderHandle = jobRender.Schedule(total_number_of_meshes, 256); + jobRenderHandle.Complete(); + + // clean up NativeArray + mesh_index_to_element_idx_and_element.Dispose(); + for (int i = 0; i < Elemental.number_of_elements; i++) + { + vertex_index_lists[i].Dispose(); + } + + z = 0; + for (int i = 0; i < number_of_elements; i++) + { + element_number_of_meshes = meshes_per_element[i]; + for (int j = 0; j < element_number_of_meshes; j++) + { + data = job_mesh_data[z]; + data.subMeshCount = 1; + data.SetSubMesh(0, new SubMeshDescriptor(0, data.GetIndexData().Length)); + z++; + } + + } + + // if the list has changed, copy meshes to a new array so we can write mesh data to them + if (this.mesh_array.Length != this.mesh_list.Count) + { + this.mesh_array = this.mesh_list.ToArray(); + } + + + // apply mesh data to meshes + Mesh.ApplyAndDisposeWritableMeshData(job_mesh_data, this.mesh_array); //MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontNotifyMeshUsers); + + // recalculate bounds of the meshes + z = 0; + for (int i = 0; i < number_of_elements; i++) + { + + Element element = (Element)i; + element_number_of_meshes = meshes_per_element[i]; + for (int j = 0; j < element_number_of_meshes; j++) + { + this.mesh_array[z].RecalculateNormals(); + this.mesh_array[z].RecalculateBounds(); + MeshFilter mf = this.meshFilters[element][j]; + MeshRenderer mr = this.meshRenderers[element][j]; + mr.material = element_materials[(int)element]; + mf.sharedMesh = this.mesh_array[z]; + z++; + } + } + } + + /// + /// Calculate the next state of the automata + /// + public override void CalculateAndRenderNextGridState() + { + CalculateNextGridState(); + RenderNextGridState(); + } + + + + private void OnApplicationQuit() + { + + try + { + this.cell_grid.Dispose(); + } + catch + { + + } + + + try + { + this.block_grid.Dispose(); + } + catch + { + + } + + try + { + for (int i = 0; i < this.vertex_index_lists.Length; i++) + { + this.vertex_index_lists[i].Dispose(); + } + } + catch + { + + } + + try + { + this.neighbor_offsets_int3.Dispose(); + } + catch + { + + } + + try + { + this.world_quad_vertices.Dispose(); + } + catch + { + + } + + + } + + + + + + +} diff --git a/Assets/Scripts/Automata/Rendering/CPU (Mesh)/WorldAutomatonCPU.cs.meta b/Assets/Scripts/Automata/Rendering/CPU (Mesh)/WorldAutomatonCPU.cs.meta new file mode 100644 index 0000000..4e4ea7f --- /dev/null +++ b/Assets/Scripts/Automata/Rendering/CPU (Mesh)/WorldAutomatonCPU.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 98e8668c309b01c46a9377d074507651 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Automata/VoxelAutomaton.cs b/Assets/Scripts/Automata/VoxelAutomaton.cs new file mode 100644 index 0000000..3f027bb --- /dev/null +++ b/Assets/Scripts/Automata/VoxelAutomaton.cs @@ -0,0 +1,283 @@ +using System.Collections; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; + + +public struct CellInfo +{ + public VoxelType current_state; + public VoxelType next_state; + public int last_frame_modified; + +} + +public abstract class VoxelAutomaton : MonoBehaviour +{ + // CPU + public NativeArray> cell_grid; // stores data for each cell, for CPU only + + + + public int frame = 0; // how many steps has the automaton computed + public float timer = 0; + public int3 automaton_dimensions; + + + public bool IsOutOfBounds(int3 index) + { + return VoxelUtils.IsOutOfBounds(index.x, index.y, index.z, this.automaton_dimensions); + } + + public bool IsOutOfBounds(int x, int y, int z) + { + return VoxelUtils.IsOutOfBounds(x, y, z, this.automaton_dimensions); + } + + + + + + + + /* Setters and getters for Native Arrays + * + */ + /// + /// Set the current state of a cell. Also flags the cell as modified during this frame. + /// + /// + /// + /// + /// + public static void SetCellNextState(NativeArray> grid_state, int3 automaton_dimensions, int frame, int x, int y, int z, VoxelType state) + { + int i = VoxelUtils.Index_FlatFromint3(x, y, z, automaton_dimensions); + SetCellNextState(grid_state, frame, i, state); + } + + /// + /// Set the current state of a cell. Also flags the cell as modified during this frame. + /// + /// + /// + /// + /// + public static void SetCellNextState(NativeArray> grid_state, int frame, int i, VoxelType state) + { + CellInfo value = grid_state[i]; + value.next_state = state; + value.last_frame_modified = frame; + grid_state[i] = value; + } + + /// + /// Get the current state of a cell + /// + /// + /// + /// + /// + public static VoxelType GetCellNextState(NativeArray> grid_state, int3 automaton_dimensions, int x, int y, int z) + { + int i = VoxelUtils.Index_FlatFromint3(x, y, z, automaton_dimensions); + return GetCellNextState(grid_state, i); + } + + /// + /// Get the current state of a cell + /// + /// + /// + /// + /// + public static VoxelType GetCellNextState(NativeArray> grid_state, int i) + { + return grid_state[i].next_state; + } + + /// + /// Get the current state of a cell + /// + /// + /// + /// + /// + public static VoxelType GetCellCurrentState(NativeArray> grid_state, int i) + { + return (VoxelType)grid_state[i].current_state; + } + + /// + /// Get the current state of a cell + /// + /// + /// + /// + /// + public static VoxelType GetCellCurrentState(NativeArray> grid_state, int3 automaton_dimensions, int x, int y, int z) + { + int i = VoxelUtils.Index_FlatFromint3(x, y, z, automaton_dimensions); + return GetCellCurrentState(grid_state, i); + } + + + /// + /// Returns a vector 3 where: + /// x is the current state + /// y is whether this cell was modified this frame + /// z is the previous state + /// + /// + /// + /// + /// + public static CellInfo GetCellInfo(NativeArray> grid_state, int i) + { + return grid_state[i]; + } + + /// + /// Returns a vector 3 where: + /// x is the current state + /// y is whether this cell was modified this frame + /// z is the previous state + /// + /// + /// + /// + /// + public static CellInfo GetCellInfo(NativeArray> grid_state, int3 automaton_dimensions, int x, int y, int z) + { + return GetCellInfo(grid_state, VoxelUtils.Index_FlatFromint3(x, y, z, automaton_dimensions)); + } + + + //===================== + + /// + /// Get the current state of a cell + /// + /// + /// + /// + /// + public virtual VoxelType GetCellNextState(int i) + { + return GetCellNextState(this.cell_grid, i); + } + + /// + /// Get the current state of a cell + /// + /// + /// + /// + /// + public virtual VoxelType GetCellCurrentState(int i) + { + return GetCellCurrentState(this.cell_grid, i); + } + + + /// + /// Returns a vector 3 where: + /// x is the current state + /// y is whether this cell was modified this frame + /// z is the previous state + /// + /// + /// + /// + /// + public virtual CellInfo GetCellInfo(int i) + { + return GetCellInfo(this.cell_grid, i); + } + + /// + /// Set the current state of a cell. Also flags the cell as modified during this frame. + /// + /// + /// + public virtual void SetCellNextState(int i, VoxelType state) + { + SetCellNextState(this.cell_grid, this.frame, i, state); + } + + + //===================== + + + /// + /// Get the current state of a cell + /// + /// + /// + /// + /// + public virtual VoxelType GetCellNextState(int x, int y, int z) + { + int i = VoxelUtils.Index_FlatFromint3(x, y, z, this.automaton_dimensions); + return GetCellNextState(i); + } + + /// + /// Get the current state of a cell + /// + /// + /// + /// + /// + public virtual VoxelType GetCellCurrentState(int x, int y, int z) + { + int i = VoxelUtils.Index_FlatFromint3(x, y, z, this.automaton_dimensions); + return GetCellCurrentState(i); + } + + + /// + /// Returns a vector 3 where: + /// x is the current state + /// y is whether this cell was modified this frame + /// z is the previous state + /// + /// + /// + /// + /// + public virtual CellInfo GetCellInfo(int x, int y, int z) + { + return GetCellInfo(this.cell_grid, this.automaton_dimensions, x, y, z); + } + + /// + /// Set the current state of a cell. Also flags the cell as modified during this frame. + /// + /// + /// + /// + /// + public virtual void SetCellNextState(int x, int y, int z, VoxelType state) + { + SetCellNextState(this.cell_grid, this.automaton_dimensions, this.frame, x, y, z, state); + } + + //===================== + + /// + /// Returns a vector 3 where: + /// x is the current state + /// y is whether this cell was modified this frame + /// z is the previous state + /// + /// + /// + /// + /// + public virtual CellInfo GetCellInfo(int3 index) + { + return GetCellInfo(index.x, index.y, index.z); + } +} diff --git a/Assets/Scripts/Automata/VoxelAutomaton.cs.meta b/Assets/Scripts/Automata/VoxelAutomaton.cs.meta new file mode 100644 index 0000000..1f507ea --- /dev/null +++ b/Assets/Scripts/Automata/VoxelAutomaton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d785ed3a0508d745a235c3fa9507961 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Automata/WorldAutomaton.cs b/Assets/Scripts/Automata/WorldAutomaton.cs new file mode 100644 index 0000000..b28a455 --- /dev/null +++ b/Assets/Scripts/Automata/WorldAutomaton.cs @@ -0,0 +1,188 @@ +using System; +using Unity.Mathematics; +using UnityEngine; +using static WorldAutomaton.Elemental; + + +using WorldCellInfo = CellInfo; + + +public abstract class WorldAutomaton : VoxelAutomaton +{ + public struct Elemental + { + public static int number_of_elements = Enum.GetValues(typeof(Element)).Length; + public enum Element : int + { + Empty, + Stone, + Sand, + Water, + Lava, + Steam + }; + + public static bool IsSolid(Element element) + { + return element == Element.Stone + || element == Element.Sand; + } + + public static bool IsLiquid(Element element) + { + return element == Element.Water + || element == Element.Lava; + } + + public static bool IsGas(Element element) + { + return element == Element.Steam; + } + } + + + + + /* + * Automata data and config + */ + public int number_of_blocks; + public int automaton_size; + public int neighborhood_size; + public Unity.Mathematics.Random random_number_generator; + public GameObject light; + + [SerializeField] public int brush_size = 3; // 0 is 1 block, 1 is all neighbors in 1 block, 2 is all neighbor in 2 blocks, etc. + //graphers + [SerializeField] public PerlinGrapher surface_grapher; + + public bool setup = false; + + // Start is called before the first frame update + // initializes the automaton + public void Start() + { + if (!setup) + { + this.automaton_dimensions = GlobalConfig.WORLD_AUTOMATON_DIMENSIONS; + this.automaton_size = this.automaton_dimensions.x * this.automaton_dimensions.y * this.automaton_dimensions.z; + this.neighborhood_size = 2 * 2 * 2; + this.random_number_generator = new Unity.Mathematics.Random(1); + (WorldCellInfo[] cell_grid, int3[] block_grid) = GenerateAndInitializeWorld(); + Setup(cell_grid, block_grid); + } + } + + private void Awake() + { + // limit framerate to prevent GPU from going overdrive for no reason + QualitySettings.vSyncCount = 0; // VSync must be disabled + Application.targetFrameRate = 45; + } + + + /// + /// Generate initial voxel information, and store indices of blocks + /// + /// + (WorldCellInfo[], int3[]) GenerateAndInitializeWorld() + { + int water_level = 4; + + WorldCellInfo[] cell_grid = new WorldCellInfo[this.automaton_size]; + int3[] block_grid = new int3[this.automaton_size / this.neighborhood_size]; + int block_grid_idx = 0; + int3 index; + WorldCellInfo value; + Element state; + // initial setting of cells + for (int i = 0; i < this.automaton_size; i++) + { + index = VoxelUtils.Index_int3FromFlat(i, this.automaton_dimensions); + + int surface_height = (int)VoxelUtils.FractalBrownianNoise(index.x, + index.z, + surface_grapher.octaves, + surface_grapher.scale, + surface_grapher.heightScale, + surface_grapher.heightOffset); + + + + if (index.y <= surface_height) + { + if (this.random_number_generator.NextFloat() <= surface_grapher.probability) + { + state = Element.Stone; + } + else + { + state = Element.Sand; + } + + } + else if (index.y < water_level) + { + state = Element.Water; + } + else + { + state = Element.Empty; + } + + value = cell_grid[i]; + value.current_state = state; + value.next_state = value.current_state; + value.last_frame_modified = this.frame; + + + + cell_grid[i] = value; + + if (index.x % 2 == 0 && index.y % 2 == 0 && index.z % 2 == 0) + { + block_grid[block_grid_idx] = index; + block_grid_idx++; + } + } + + return (cell_grid, block_grid); + } + + + public abstract void Setup(WorldCellInfo[] cell_grid, int3[] block_grid); + + + + public abstract void RunMouseButtonBehaviors(Element state, int brush_size); + + public abstract void CalculateAndRenderNextGridState(); + + private void Update() + { + timer += Time.deltaTime; + + if (timer > GlobalConfig.WORLD_AUTOMATA_SECONDS_PER_STEP) + { + CalculateAndRenderNextGridState(); + ProcessUserInput(); + this.frame++; + timer = 0; + } + + } + + private void ProcessUserInput() + { + if (Input.GetMouseButton(0) || Input.GetMouseButton(1)) + { + //RunMouseButtonBehaviors(CanvasNav.GetButtonCellState(), this.brush_size); + } + } + + + + + + +} diff --git a/Assets/Scripts/Automata/WorldAutomaton.cs.meta b/Assets/Scripts/Automata/WorldAutomaton.cs.meta new file mode 100644 index 0000000..d6896e6 --- /dev/null +++ b/Assets/Scripts/Automata/WorldAutomaton.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a559a051595675c488844ae49a1065d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: