public void GenerateTreePrototypeData() { List <string> prefabNames = new List <string>(); List <GameObject> prefabs = new List <GameObject>(); System.Array.ForEach(m_ItemsToExtract, (x) => { prefabNames.Add(x.m_ItemPrefab.name); prefabs.Add(x.m_ItemPrefab); }); if (TerrainUtils.TreeHashCheck(prefabNames.ToArray())) { Debug.LogError("Tree name hash collision, fix!"); return; } GameObject[] proto = prefabs.ToArray(); List <TreeSystemPrototypeData> managed = new List <TreeSystemPrototypeData>(); for (int i = 0; i < proto.Length; i++) { GameObject prefab = proto[i]; if (PrefabUtility.GetPrefabType(prefab) != PrefabType.ModelPrefab || prefab.GetComponent <LODGroup>() == null || prefab.GetComponentInChildren <BillboardRenderer>() == null) { Debug.LogError("Invalid prefab: " + prefab.name + ". Make sure that it is a SpeedTree, that it contains a 'LODGroup' and that it has a 'BillboardRenderer' component."); continue; } TreeSystemPrototypeData data = new TreeSystemPrototypeData(); data.m_TreePrototype = prefab; // Use hash here instead of the old index data.m_TreePrototypeHash = TUtils.GetStableHashCode(prefab.name); // Instantiate LOD data that is going to be populated at runtime LOD[] lods = prefab.GetComponent <LODGroup>().GetLODs(); TreeSystemLODData[] lodData = new TreeSystemLODData[lods.Length]; // Generate some partial LOD data that doesn't have to be calculated at runtime data.m_LODData = lodData; for (int lod = 0; lod < lodData.Length; lod++) { TreeSystemLODData d = new TreeSystemLODData(); lodData[lod] = d; } data.m_MaxLod3DIndex = lodData.Length - 2; managed.Add(data); } m_ManagedPrototypes = managed.ToArray(); // Try and set the prototypes to our tree system TreeSystem t = FindObjectOfType <TreeSystem>(); if (t) { t.m_ManagedPrototypes = m_ManagedPrototypes; } }
private static void testArray <T>(Action <T[]> write, Func <T[]> read, T[] input, LengthPrefix lengthPrefix = LengthPrefix.Default) { string typ = typeof(T).Name; TUtils.Test("read/write " + typ + "[] (" + TUtils.IListToString(input) + ")", () => { bw.Position = 0; write(input); bw.Position = 0; T[] result = read(); if (input.Length != result.Length) { TUtils.WriteFail($"FAIL length not equal{input.Length}!={result.Length}"); return(TestResult.Failure); } if (TUtils.IsIListEqual(input, result)) { TUtils.WriteSucces($"OK"); return(TestResult.Success); } else { TUtils.WriteFail($"FAIL array({TUtils.IListToString(result)})"); return(TestResult.Failure); } }); }
private static void testGArray <T>(T[] input, LengthPrefix lengthPrefix = LengthPrefix.Int32) where T : unmanaged { string typ = typeof(T).Name; TUtils.Test($"read/write {typ}[].length:{lengthPrefix} ({TUtils.IListToString(input)})", () => { bw.Position = 0; bw.WriteArray(input, lengthPrefix); bw.Position = 0; T[] result = br.ReadArray <T>(lengthPrefix); if (input.Length != result.Length) { TUtils.WriteFail($"FAIL length not equal{input.Length}!={result.Length}"); return(TestResult.Failure); } if (TUtils.IsIListEqual(input, result)) { TUtils.WriteSucces($"OK"); return(TestResult.Success); } else { TUtils.WriteFail($"FAIL array({TUtils.IListToString(result)})"); return(TestResult.Failure); } }); }
private static void testLists() { int size = 8; Random rnd = new Random(1); byte[] data0 = new byte[size]; for (int i = 0; i < size; i++) { data0[i] = (byte)(rnd.NextDouble() * 255f); } TUtils.Test("Read to new List", () => { var bw = new BinaryViewWriter(); bw.WriteIList(data0); bw.Dispose(); var file = bw.ToArray(); var br = new BinaryViewReader(file); var list = new List <byte>(); br.ReadToIList(list); br.Dispose(); if (!TUtils.IsIListEqual(data0, list)) { TUtils.WriteFail($"FAIL data: {TUtils.IListToString(list)}, expected: {TUtils.IListToString(data0)}"); return(TestResult.Failure); } TUtils.WriteSucces($"OK"); return(TestResult.Success); }); TUtils.Test("Read no Prefix", () => { var bw = new BinaryViewWriter(); bw.WriteIList(data0, LengthPrefix.None); bw.Dispose(); var file = bw.ToArray(); var br = new BinaryViewReader(file); var list = new List <byte>(); br.ReadToIList(list, 0, size); br.Dispose(); if (!TUtils.IsIListEqual(data0, list)) { TUtils.WriteFail($"FAIL data: {TUtils.IListToString(list)}, expected: {TUtils.IListToString(data0)}"); return(TestResult.Failure); } TUtils.WriteSucces($"OK"); return(TestResult.Success); }); }
/** * Checks whether there is a collision within the tree's hashes that has to be remedied * * @return False in case there is no collision and true if there is a collision */ public static bool TreeHashCheck(string[] prefabNames) { HashSet <int> set = new HashSet <int>(); foreach (string prefab in prefabNames) { if (set.Contains(TUtils.GetStableHashCode(prefab))) { return(true); } set.Add(TUtils.GetStableHashCode(prefab)); } return(false); }
/** * Checks whether there is a collision within the tree's hashes that has to be remedied * * @return False in case there is no collision and true if there is a collision */ public static bool TreeHashCheck(Terrain mainTerrain) { HashSet <int> set = new HashSet <int>(); TreePrototype[] p = mainTerrain.terrainData.treePrototypes; for (int i = 0; i < p.Length; i++) { if (set.Contains(TUtils.GetStableHashCode(p[i].prefab.name))) { return(true); } set.Add(TUtils.GetStableHashCode(p[i].prefab.name)); } return(false); }
private void fillBeziers() { beziers = new Bezier <T> [controlPoints.Length]; Bezier <T> first = new Bezier <T>(); first.b0 = controlPoints[0]; first.b1 = TUtils <T> .Additate(TUtils <T> .Scalar((TUtils <T> .Subtract(controlPoints[1], controlPoints[0])), (1f / 3f)), controlPoints[0]); first.b2 = TUtils <T> .Additate(TUtils <T> .Scalar((TUtils <T> .Subtract(controlPoints[1], controlPoints[0])), (2f / 3f)), controlPoints[0]); beziers[0] = first; Bezier <T> last = new Bezier <T>(); last.b1 = TUtils <T> .Additate(TUtils <T> .Scalar((TUtils <T> .Subtract(controlPoints[0], controlPoints[controlPoints.Length - 1])), (1f / 3f)), controlPoints[controlPoints.Length - 1]); last.b2 = TUtils <T> .Additate(TUtils <T> .Scalar((TUtils <T> .Subtract(controlPoints[0], controlPoints[controlPoints.Length - 1])), (2f / 3f)), controlPoints[controlPoints.Length - 1]); last.b3 = controlPoints[0]; beziers[beziers.Length - 1] = last; for (int i = 1; i < beziers.Length - 1; i++) { beziers[i] = new Bezier <T>(); beziers[i].b1 = TUtils <T> .Additate(TUtils <T> .Scalar((TUtils <T> .Subtract(controlPoints[i + 1], controlPoints[i])), (1f / 3f)), controlPoints[i]); beziers[i].b2 = TUtils <T> .Additate(TUtils <T> .Scalar((TUtils <T> .Subtract(controlPoints[i + 1], controlPoints[i])), (2f / 3f)), controlPoints[i]); } beziers[beziers.Length - 1].b3 = TUtils <T> .Additate(TUtils <T> .Scalar((TUtils <T> .Subtract(beziers[0].b1, beziers[beziers.Length - 1].b2)), 0.5f), beziers[beziers.Length - 1].b2); beziers[0].b0 = beziers[beziers.Length - 1].b3; for (int i = 0; i < beziers.Length - 1; i++) { beziers[i].b3 = TUtils <T> .Additate(TUtils <T> .Scalar((TUtils <T> .Subtract(beziers[i + 1].b1, beziers[i].b2)), 0.5f), beziers[i].b2); beziers[i + 1].b0 = beziers[i].b3; } }
private static void testString(string str, LengthPrefix lengthPrefix = LengthPrefix.Default, CharSizePrefix charSizePrefix = CharSizePrefix.Default) { TUtils.Test($"read/write string[{charSizePrefix}].length:{lengthPrefix} ({str})", () => { bw.Position = 0; bw.WriteString(str, lengthPrefix, charSizePrefix); bw.Position = 0; string result = br.ReadString(lengthPrefix, charSizePrefix); if (result.Equals(str)) { TUtils.WriteSucces("OK"); return(TestResult.Success); } else { TUtils.WriteFail($"FAIL \"{result}\""); return(TestResult.Failure); } }); }
private static void testSTyp <T>(T input) { string typ = typeof(T).Name; TUtils.Test("read/write " + typ + " (" + input + ")", () => { bw.Position = 0; bw.Serialize(input); var size = bw.Position; bw.Position = 0; T result = br.Deserialize <T>(); if (result.Equals(input)) { TUtils.WriteSucces($"OK {size}b"); return(TestResult.Success); } else { TUtils.WriteFail($"{result}"); return(TestResult.Failure); } }); }
private static void testTyp <T>(Action <T> write, Func <T> read, T input) { string typ = typeof(T).Name; TUtils.Test("read/write " + typ + " (" + input + ")", () => { bw.Position = 0; write(input); bw.Position = 0; T result = read(); if (result.Equals(input)) { TUtils.WriteSucces("OK"); return(TestResult.Success); } else { TUtils.WriteFail($"{result}"); return(TestResult.Failure); } }); }
// TODO: see from the list which trees fit in here private TreeSystemTerrain ProcessTerrain(Terrain terrain, int cellSize, GameObject cellHolder, List <GameObject> extraTrees) { TreeSystemTerrain systemTerrain = new TreeSystemTerrain(); // Set system terrain data systemTerrain.m_ManagedTerrain = terrain; systemTerrain.m_ManagedTerrainBounds = terrain.GetComponent <TerrainCollider>().bounds; systemTerrain.m_ManagedTerrainLocalToWorld = terrain.transform.localToWorldMatrix; systemTerrain.m_ManagedTerrainWorldToLocal = terrain.transform.worldToLocalMatrix; systemTerrain.m_ManagedTerrainSizes = terrain.terrainData.size; systemTerrain.m_CellCount = TerrainUtils.GetCellCount(terrain, cellSize); systemTerrain.m_CellSize = cellSize; int cellCount; BoxCollider[,] collidersBox; SphereCollider[,] collidersSphere; // Gridify terrain TerrainUtils.Gridify(terrain, cellSize, out cellCount, out collidersBox, out collidersSphere, cellHolder, null); // Temporary structured data TreeSystemStructuredTrees[,] str = new TreeSystemStructuredTrees[cellCount, cellCount]; List <TreeSystemStoredInstance>[,] strInst = new List <TreeSystemStoredInstance> [cellCount, cellCount]; List <TreeSystemStructuredTrees> list = new List <TreeSystemStructuredTrees>(); // Insantiate the required data for (int r = 0; r < cellCount; r++) { for (int c = 0; c < cellCount; c++) { TreeSystemStructuredTrees s = new TreeSystemStructuredTrees(); // Set the bounds, all in world space s.m_BoundsBox = collidersBox[r, c].bounds; s.m_BoundsSphere = new TreeSystemBoundingSphere(s.m_BoundsBox.center, collidersSphere[r, c].radius); // Set it's new position s.m_Position = new RowCol(r, c); str[r, c] = s; strInst[r, c] = new List <TreeSystemStoredInstance>(); list.Add(s); } } // Delete cells since they might cause physics problems if (m_DeleteCellsAfterGridify) { for (int i = 0; i < cellCount; i++) { for (int j = 0; j < cellCount; j++) { DestroyImmediate(collidersBox[i, j].gameObject); } } } int treeInstancesCount = 0, treeExtraCount = 0; TreeInstance[] terrainTreeInstances = terrain.terrainData.treeInstances; TreePrototype[] terrainTreeProto = terrain.terrainData.treePrototypes; Vector3 sizes = terrain.terrainData.size; for (int i = 0; i < terrainTreeInstances.Length; i++) { GameObject proto = terrainTreeProto[terrainTreeInstances[i].prototypeIndex].prefab; if (ShouldUsePrefab(proto) < 0) { continue; } treeInstancesCount++; // Get bounds for that mesh Bounds b = proto.transform.Find(proto.name + "_LOD0").gameObject.GetComponent <MeshFilter>().sharedMesh.bounds; // Calculate this from normalized terrain space to terrain's local space so that our row/col info are correct. // Do the same when testing for cell row/col in which the player is, transform to terrain local space Vector3 pos = TerrainUtils.TerrainToTerrainPos(terrainTreeInstances[i].position, terrain); int row = Mathf.Clamp(Mathf.FloorToInt(pos.x / sizes.x * cellCount), 0, cellCount - 1); int col = Mathf.Clamp(Mathf.FloorToInt(pos.z / sizes.z * cellCount), 0, cellCount - 1); pos = TerrainUtils.TerrainToWorldPos(terrainTreeInstances[i].position, terrain); Vector3 scale = new Vector3(terrainTreeInstances[i].widthScale, terrainTreeInstances[i].heightScale, terrainTreeInstances[i].widthScale); float rot = terrainTreeInstances[i].rotation; int hash = TUtils.GetStableHashCode(proto.name); Matrix4x4 mtx = Matrix4x4.TRS(pos, Quaternion.Euler(0, rot * Mathf.Rad2Deg, 0), scale); TreeSystemStoredInstance inst = new TreeSystemStoredInstance(); inst.m_TreeHash = hash; inst.m_PositionMtx = mtx; inst.m_WorldPosition = pos; inst.m_WorldScale = scale; inst.m_WorldRotation = rot; inst.m_WorldBounds = TUtils.LocalToWorld(ref b, ref mtx); strInst[row, col].Add(inst); } List <GameObject> containedTrees = new List <GameObject>(); // Change if we're going to use something diff than 50 for max extent Bounds terrainExtendedBounds = systemTerrain.m_ManagedTerrainBounds; terrainExtendedBounds.Expand(new Vector3(0, 50, 0)); // Same as a instance with minor diferences for (int i = 0; i < extraTrees.Count; i++) { GameObject treeInstance = extraTrees[i]; // If the terrain contains the stuff if (terrainExtendedBounds.Contains(treeInstance.transform.position) == false) { continue; } treeExtraCount++; // Add the tree to the list of trees for removal containedTrees.Add(treeInstance); // Owner GameObject proto = GetPrefabOwner(treeInstance); // Get bounds for that mesh Bounds b = proto.transform.Find(proto.name + "_LOD0").gameObject.GetComponent <MeshFilter>().sharedMesh.bounds; // Calculate this from normalized terrain space to terrain's local space so that our row/col info are correct. // Do the same when testing for cell row/col in which the player is, transform to terrain local space Vector3 pos = TerrainUtils.TerrainToTerrainPos(TerrainUtils.WorldPosToTerrain(treeInstance.transform.position, terrain), terrain); int row = Mathf.Clamp(Mathf.FloorToInt(pos.x / sizes.x * cellCount), 0, cellCount - 1); int col = Mathf.Clamp(Mathf.FloorToInt(pos.z / sizes.z * cellCount), 0, cellCount - 1); pos = treeInstance.transform.position; Vector3 scale = treeInstance.transform.localScale; float rot = treeInstance.transform.rotation.eulerAngles.y * Mathf.Deg2Rad; // Set the hash int hash = TUtils.GetStableHashCode(proto.name); // Set the mtx Matrix4x4 mtx = Matrix4x4.TRS(pos, Quaternion.Euler(0, rot * Mathf.Rad2Deg, 0), scale); TreeSystemStoredInstance inst = new TreeSystemStoredInstance(); inst.m_TreeHash = hash; inst.m_PositionMtx = mtx; inst.m_WorldPosition = pos; inst.m_WorldScale = scale; inst.m_WorldRotation = rot; inst.m_WorldBounds = TUtils.LocalToWorld(ref b, ref mtx); strInst[row, col].Add(inst); } // Remove the items from the extra trees foreach (GameObject tree in containedTrees) { extraTrees.Remove(tree); } // Generate the mesh that contain all the billboards for (int r = 0; r < cellCount; r++) { for (int c = 0; c < cellCount; c++) { if (strInst[r, c].Count <= 0) { continue; } // Sort based on the tree hash so that we don't have to do many dictionary look-ups strInst[r, c].Sort((x, y) => x.m_TreeHash.CompareTo(y.m_TreeHash)); // Set the new instances str[r, c].m_Instances = strInst[r, c].ToArray(); // Build the meshes for each cell based on tree type List <TreeSystemStoredInstance> singleType = new List <TreeSystemStoredInstance>(); int lastHash = strInst[r, c][0].m_TreeHash; foreach (TreeSystemStoredInstance inst in strInst[r, c]) { // If we have a new hash, consume all the existing instances if (inst.m_TreeHash != lastHash) { TreeSystemPrototypeData data = GetPrototypeWithHash(lastHash); if (ShouldBuildBillboardBatch(data.m_TreePrototype)) { BuildTreeTypeCellMesh(cellHolder, str[r, c], singleType, data); } singleType.Clear(); // Update the hash lastHash = inst.m_TreeHash; } // Add them to a list and when the hash changes begin the next generation singleType.Add(inst); } if (singleType.Count > 0) { TreeSystemPrototypeData data = GetPrototypeWithHash(singleType[0].m_TreeHash); if (ShouldBuildBillboardBatch(data.m_TreePrototype)) { BuildTreeTypeCellMesh(cellHolder, str[r, c], singleType, data); } singleType.Clear(); } } } // Set the cells that contain the trees to the system terrain systemTerrain.m_Cells = list.ToArray(); // Print extraction data Debug.Log("Extracted for terrain: " + terrain.name + " instance trees: " + treeInstancesCount + " extra trees: " + treeExtraCount); // Return it return(systemTerrain); }
public static void Run() { stream = new MemoryStream(); bw = new BinaryViewWriter(stream); br = new BinaryViewReader(stream); TUtils.WriteTitle("Run tests...\n"); TUtils.WriteTitle("test types"); testTyp(bw.WriteBoolean, br.ReadBoolean, false, true); testTyp(bw.WriteChar, br.ReadChar, char.MinValue, char.MaxValue); testTyp(bw.WriteByte, br.ReadByte, byte.MinValue, byte.MaxValue); testTyp(bw.WriteSByte, br.ReadSByte, sbyte.MinValue, sbyte.MaxValue); testTyp(bw.WriteUInt16, br.ReadUInt16, ushort.MinValue, ushort.MaxValue); testTyp(bw.WriteInt16, br.ReadInt16, short.MinValue, short.MaxValue); testTyp(bw.WriteUInt32, br.ReadUInt32, uint.MinValue, uint.MaxValue); testTyp(bw.WriteInt32, br.ReadInt32, int.MinValue, int.MaxValue); testTyp(bw.WriteUInt64, br.ReadUInt64, ulong.MinValue, ulong.MaxValue); testTyp(bw.WriteInt64, br.ReadInt64, long.MinValue, long.MaxValue); testTyp(bw.WriteSingle, br.ReadSingle, float.MinValue, float.MaxValue); testTyp(bw.WriteDouble, br.ReadDouble, double.MinValue, double.MaxValue); testTyp(bw.WriteDecimal, br.ReadDecimal, decimal.MinValue, decimal.MaxValue); TUtils.WriteTitle("test string"); testString("TestString123", LengthPrefix.Default, CharSizePrefix.Default); testString("TestString123", LengthPrefix.Byte, CharSizePrefix.Byte); testString("TestString123", LengthPrefix.UInt32, CharSizePrefix.Char); testString("Ä'*Ü-.,><%§ÃoÜ╝ô○╝+"); TUtils.WriteTitle("test unmanaged types"); testGTyp(false, true); testGTyp(char.MinValue, char.MaxValue); testGTyp(byte.MinValue, byte.MaxValue); testGTyp(sbyte.MinValue, sbyte.MaxValue); testGTyp(ushort.MinValue, ushort.MaxValue); testGTyp(short.MinValue, short.MaxValue); testGTyp(uint.MinValue, uint.MaxValue); testGTyp(int.MinValue, int.MaxValue); testGTyp(ulong.MinValue, ulong.MaxValue); testGTyp(long.MinValue, long.MaxValue); testGTyp(float.MinValue, float.MaxValue); testGTyp(double.MinValue, double.MaxValue); testGTyp(decimal.MinValue, decimal.MaxValue); testGTyp(new TUtils.Struct() { A = 42, B = 3.6f }); testGTyp(new DateTime(2020, 07, 20, 15, 54, 24)); testGTyp(new Point(10, 42)); testGTyp(new RectangleF(10, 42, 25.5f, 23)); TUtils.WriteTitle("test serializable types"); testSTyp(42); testSTyp("Hello World"); testSTyp(new DateTime(2000, 10, 20)); testSTyp(new DateTime(2020, 07, 20, 15, 54, 24)); testSTyp(new Point(2000, 10)); testSTyp(new RectangleF(10, 42, 25.5f, 23)); TUtils.WriteTitle("test arrays"); testGArray(new byte[] { 0, 2, 4, 6 }); testGArray(new byte[] { 0, 2, 4, 6 }, LengthPrefix.Int64); testGArray(new int[] { 0, -2, 4, -6 }); testGArray(new float[] { 0, -2.5f, 4.25f, -6.66f }); testGArray(new TUtils.Struct[] { new TUtils.Struct() { A = 42, B = 3.6f }, new TUtils.Struct() { A = 36, B = 1.666f } }); testArray(bw.WriteStringArray, br.ReadStringArray, new string[] { "ab", "cd", "ef", "gh" }); TUtils.WriteTitle("test IList"); testLists(); TUtils.WriteTitle("test compresion"); testCompression(); TUtils.WriteTitle("test map"); testMap(64, false); TUtils.WriteTitle("test map compressed"); testMap(64, true); TUtils.WriteTitle("test speed"); testSpeed(); TUtils.WriteResults(); }
public void GenerateTreePrototypeData() { if (TerrainUtils.TreeHashCheck(m_MainManagedTerrain)) { Debug.LogError("Tree name hash collision, fix!"); return; } GameObject[] proto = m_TreeToExtractPrefabs; List <TreeSystemPrototypeData> managed = new List <TreeSystemPrototypeData>(); for (int i = 0; i < proto.Length; i++) { GameObject prefab = proto[i]; if (PrefabUtility.GetPrefabType(prefab) != PrefabType.ModelPrefab || prefab.GetComponent <LODGroup>() == null || prefab.GetComponentInChildren <BillboardRenderer>() == null) { Debug.LogError("Invalid prefab: " + prefab.name + ". Make sure that it is a SpeedTree, that it contains a 'LODGroup' and that it has a 'BillboardRenderer' component."); continue; } TreeSystemPrototypeData data = new TreeSystemPrototypeData(); data.m_TreePrototype = prefab; // Use hash here instead of the old index data.m_TreePrototypeHash = TUtils.GetStableHashCode(prefab.name); if (m_UseXMLData) { TextAsset textData = AssetDatabase.LoadAssetAtPath <TextAsset>(m_TreeXMLStorePath + "/" + prefab.name + ".xml"); if (textData != null) { data.m_TreeBillboardData = textData; } else { Debug.LogError("Could not find XML data for: " + data.m_TreePrototype.name); } } // Instantiate LOD data that is going to be populated at runtime LOD[] lods = prefab.GetComponent <LODGroup>().GetLODs(); TreeSystemLODData[] lodData = new TreeSystemLODData[lods.Length]; // Generate some partial LOD data that doesn't have to be calculated at runtime data.m_LODData = lodData; for (int lod = 0; lod < lodData.Length; lod++) { TreeSystemLODData d = new TreeSystemLODData(); lodData[lod] = d; } data.m_MaxLodIndex = lodData.Length - 1; data.m_MaxLod3DIndex = lodData.Length - 2; managed.Add(data); } m_ManagedPrototypes = managed.ToArray(); // Try and set the prototypes to our tree system TreeSystem t = FindObjectOfType <TreeSystem>(); if (t) { t.m_ManagedPrototypes = m_ManagedPrototypes; } }
private static void testMap(int size, bool compressed) { for (int it = 0; it < 6; it++) { byte[] mapLayer1 = new byte[size]; byte[] mapLayer2 = new byte[size]; byte[] mapLayer3 = new byte[size]; Random rnd = new Random(1); for (int i = 0; i < size; i++) { mapLayer1[i] = (byte)(rnd.NextDouble() * 255f); } rnd = new Random(2); for (int i = 0; i < size; i++) { mapLayer2[i] = (byte)(rnd.NextDouble() * 2f); } TUtils.Test($"save map {size}x{size}", () => { using (var binaryView = new BinaryViewWriter("test.dat")) { if (compressed) { binaryView.CompressAll(); } binaryView.WriteString("map"); binaryView.WriteInt32(size); binaryView.WriteSingle(0.45f); binaryView.WriteArray(mapLayer1); binaryView.WriteArray(mapLayer2); binaryView.WriteArray(mapLayer3); } TUtils.WriteSucces($"OK {new FileInfo("test.dat").Length}b"); return(TestResult.Success); }); TUtils.Test($"load map {size}x{size}", () => { bool result = true; using (var binaryView = new BinaryViewReader("test.dat")) { if (compressed) { binaryView.DecompressAll(); } result &= binaryView.ReadString() == "map"; result &= binaryView.ReadInt32() == size; result &= binaryView.ReadSingle() == 0.45f; result &= TUtils.IsIListEqual(mapLayer1, binaryView.ReadArray <byte>()); result &= TUtils.IsIListEqual(mapLayer2, binaryView.ReadArray <byte>()); result &= TUtils.IsIListEqual(mapLayer3, binaryView.ReadArray <byte>()); } if (result) { TUtils.WriteSucces("OK"); return(TestResult.Success); } else { TUtils.WriteFail("FAIL"); return(TestResult.Failure); } }); size *= 2; } }
public void GenerateTreePrototypeData() { if (TerrainUtils.TreeHashCheck(m_MainManagedTerrain)) { Log.e("Tree name hash collision, fix!"); return; } TreePrototype[] proto = m_MainManagedTerrain.terrainData.treePrototypes; List <TreeSystemPrototypeData> managed = new List <TreeSystemPrototypeData>(); for (int i = 0; i < proto.Length; i++) { if (ShouldUsePrefab(proto[i].prefab) >= 0) { GameObject prefab = proto[i].prefab; TreeSystemPrototypeData data = new TreeSystemPrototypeData(); data.m_TreePrototype = prefab; // Use hash here instead of the old index data.m_TreePrototypeHash = TUtils.GetStableHashCode(proto[i].prefab.name); if (m_UseXMLData) { TextAsset textData = AssetDatabase.LoadAssetAtPath <TextAsset>(m_TreeXMLStorePath + "/" + proto[i].prefab.name + ".xml"); if (textData != null) { data.m_TreeBillboardData = textData; } else { Debug.LogError("Could not find XML data for: " + data.m_TreePrototype.name); } } // Instantiate LOD data that is going to be populated at runtime LOD[] lods = prefab.GetComponent <LODGroup>().GetLODs(); TreeSystemLODData[] lodData = new TreeSystemLODData[lods.Length]; // Generate some partial LOD data that doesn't have to be calculated at runtime data.m_LODData = lodData; for (int lod = 0; lod < lodData.Length; lod++) { TreeSystemLODData d = new TreeSystemLODData(); lodData[lod] = d; } data.m_MaxLodIndex = lodData.Length - 1; data.m_MaxLod3DIndex = lodData.Length - 2; managed.Add(data); } } m_ManagedPrototypes = managed.ToArray(); // Try and set the prototypes to our tree system TreeSystem t = FindObjectOfType <TreeSystem>(); if (t) { t.m_ManagedPrototypes = m_ManagedPrototypes; } }
private List <Vector2> ExtractBillboards(XmlElement bills, bool vertical) { XmlElement verticalBills = bills; List <Vector2> allUv = new List <Vector2>(); if (vertical) { foreach (XmlElement node in verticalBills.ChildNodes) { XmlElement elem = node; bool rotated = bool.Parse(elem.GetAttribute("Rotated")); Log.i("Rotated: " + rotated); string[] u = node["TexcoordU"].InnerText.Trim().Split(' '); string[] v = node["TexcoordV"].InnerText.Trim().Split(' '); Log.i("UV data: " + TUtils.ToString(u) + " " + TUtils.ToString(v)); if (v.Length != u.Length || v.Length != 4) { Debug.LogError("Something bad went parsing: " + u + " " + v); continue; } List <Vector2> uv = new List <Vector2>(); for (int j = 0; j < u.Length; j++) { uv.Add(new Vector2(float.Parse(u[j]), float.Parse(v[j]))); } Log.i("Extracted uv: " + TUtils.ToString(uv)); allUv.AddRange(uv); } } else { string[] u = bills["TexcoordU"].InnerText.Trim().Split(' '); string[] v = bills["TexcoordV"].InnerText.Trim().Split(' '); Log.i("UV data: " + TUtils.ToString(u) + " " + TUtils.ToString(v)); if (v.Length != u.Length || v.Length != 4) { Debug.LogError("Something bad went parsing: " + u + " " + v); } List <Vector2> uv = new List <Vector2>(); for (int j = 0; j < u.Length; j++) { uv.Add(new Vector2(float.Parse(u[j]), float.Parse(v[j]))); } Log.i("Extracted uv: " + TUtils.ToString(uv)); allUv.AddRange(uv); } return(allUv); }
private TreeSystemTerrain ProcessTerrain(Terrain terrain, int cellSize, GameObject cellHolder) { TreeSystemTerrain systemTerrain = new TreeSystemTerrain(); // Set system terrain data systemTerrain.m_ManagedTerrain = terrain; systemTerrain.m_ManagedTerrainBounds = terrain.GetComponent <TerrainCollider>().bounds; systemTerrain.m_CellCount = TerrainUtils.GetCellCount(terrain, cellSize); systemTerrain.m_CellSize = cellSize; int cellCount; BoxCollider[,] collidersBox; SphereCollider[,] collidersSphere; // Gridify terrain TerrainUtils.Gridify(terrain, cellSize, out cellCount, out collidersBox, out collidersSphere, cellHolder, null); // Temporary structured data TreeSystemStructuredTrees[,] str = new TreeSystemStructuredTrees[cellCount, cellCount]; List <TreeSystemStoredInstance>[,] strInst = new List <TreeSystemStoredInstance> [cellCount, cellCount]; List <TreeSystemStructuredTrees> list = new List <TreeSystemStructuredTrees>(); // Insantiate the required data for (int r = 0; r < cellCount; r++) { for (int c = 0; c < cellCount; c++) { TreeSystemStructuredTrees s = new TreeSystemStructuredTrees(); // Set the bounds, all in world space s.m_BoundsBox = collidersBox[r, c].bounds; s.m_BoundsSphere = new TreeSystemBoundingSphere(s.m_BoundsBox.center, collidersSphere[r, c].radius); // Set it's new position s.m_Position = new RowCol(r, c); str[r, c] = s; strInst[r, c] = new List <TreeSystemStoredInstance>(); list.Add(s); } } TreeInstance[] terrainTreeInstances = terrain.terrainData.treeInstances; TreePrototype[] terrainTreeProto = terrain.terrainData.treePrototypes; Vector3 sizes = terrain.terrainData.size; for (int i = 0; i < terrainTreeInstances.Length; i++) { GameObject proto = terrainTreeProto[terrainTreeInstances[i].prototypeIndex].prefab; if (ShouldUsePrefab(proto) < 0) { continue; } // Get bounds for that mesh Bounds b = proto.transform.Find(proto.name + "_LOD0").gameObject.GetComponent <MeshFilter>().sharedMesh.bounds; // Calculate this from normalized terrain space to terrain's local space so that our row/col info are correct. // Do the same when testing for cell row/col in which the player is, transform to terrain local space Vector3 pos = TerrainUtils.TerrainToTerrainPos(terrainTreeInstances[i].position, terrain); int row = Mathf.Clamp(Mathf.FloorToInt(pos.x / sizes.x * cellCount), 0, cellCount - 1); int col = Mathf.Clamp(Mathf.FloorToInt(pos.z / sizes.z * cellCount), 0, cellCount - 1); pos = TerrainUtils.TerrainToWorldPos(terrainTreeInstances[i].position, terrain); Vector3 scale = new Vector3(terrainTreeInstances[i].widthScale, terrainTreeInstances[i].heightScale, terrainTreeInstances[i].widthScale); float rot = terrainTreeInstances[i].rotation; int hash = TUtils.GetStableHashCode(proto.name); Matrix4x4 mtx = Matrix4x4.TRS(pos, Quaternion.Euler(0, rot * Mathf.Rad2Deg, 0), scale); TreeSystemStoredInstance inst = new TreeSystemStoredInstance(); inst.m_TreeHash = hash; inst.m_PositionMtx = mtx; inst.m_WorldPosition = pos; inst.m_WorldScale = scale; inst.m_WorldRotation = rot; inst.m_WorldBounds = TUtils.LocalToWorld(ref b, ref mtx); strInst[row, col].Add(inst); } // Generate the mesh that contain all the billboards for (int r = 0; r < cellCount; r++) { for (int c = 0; c < cellCount; c++) { if (strInst[r, c].Count <= 0) { continue; } // Sort based on the tree hash so that we don't have to do many dictionary look-ups strInst[r, c].Sort((x, y) => x.m_TreeHash.CompareTo(y.m_TreeHash)); // Set the new instances str[r, c].m_Instances = strInst[r, c].ToArray(); // Build the meshes for each cell based on tree type List <TreeSystemStoredInstance> singleType = new List <TreeSystemStoredInstance>(); int lastHash = strInst[r, c][0].m_TreeHash; foreach (TreeSystemStoredInstance inst in strInst[r, c]) { // If we have a new hash, consume all the existing instances if (inst.m_TreeHash != lastHash) { TreeSystemPrototypeData data = GetPrototypeWithHash(lastHash); BuildTreeTypeCellMesh(cellHolder, str[r, c], singleType, data); singleType.Clear(); // Update the hash lastHash = inst.m_TreeHash; } // Add them to a list and when the hash changes begin the next generation singleType.Add(inst); } if (singleType.Count > 0) { TreeSystemPrototypeData data = GetPrototypeWithHash(singleType[0].m_TreeHash); BuildTreeTypeCellMesh(cellHolder, str[r, c], singleType, data); singleType.Clear(); } } } // Set the cells that contain the trees to the system terrain systemTerrain.m_Cells = list.ToArray(); // Return it return(systemTerrain); }
private static void testCompression() { int size = 8; Random rnd = new Random(1); byte[] data0 = new byte[size]; for (int i = 0; i < size; i++) { data0[i] = (byte)(rnd.NextDouble() * 255f); } byte[] data1 = new byte[size]; for (int i = 0; i < size; i++) { data1[i] = (byte)(rnd.NextDouble() * 255f); } byte[] data2 = new byte[size]; for (int i = 0; i < size; i++) { data2[i] = (byte)(rnd.NextDouble() * 255f); } TUtils.Test("Compress All", () => { var bw = new BinaryViewWriter(); bw.CompressAll(); bw.WriteArray(data0); bw.Dispose(); var file = bw.ToArray(); if (file.Length == 0) { TUtils.WriteFail($"FAIL file length is 0"); return(TestResult.Failure); } var br = new BinaryViewReader(file); br.DecompressAll(); var rdata0 = br.ReadArray <byte>(); br.Dispose(); if (!TUtils.IsIListEqual(data0, rdata0)) { TUtils.WriteFail($"FAIL data: {TUtils.IListToString(rdata0)}, expected: {TUtils.IListToString(data0)}"); return(TestResult.Failure); } TUtils.WriteSucces($"OK"); return(TestResult.Success); }); TUtils.Test("Compress Section", () => { var bw = new BinaryViewWriter(); bw.BeginDeflateSection(); bw.WriteArray(data0); bw.EndDeflateSection(); bw.Dispose(); var file = bw.ToArray(); if (file.Length == 0) { TUtils.WriteFail($"FAIL file length is 0"); return(TestResult.Failure); } var br = new BinaryViewReader(file); br.BeginDeflateSection(); var rdata0 = br.ReadArray <byte>(); br.EndDeflateSection(); br.Dispose(); if (!TUtils.IsIListEqual(data0, rdata0)) { TUtils.WriteFail($"FAIL data: {TUtils.IListToString(rdata0)}, expected: {TUtils.IListToString(data0)}"); return(TestResult.Failure); } TUtils.WriteSucces($"OK"); return(TestResult.Success); }); TUtils.Test("Compress 2 Sections", () => { var bw = new BinaryViewWriter(); bw.BeginDeflateSection(); bw.WriteArray(data0); bw.EndDeflateSection(); bw.WriteArray(data1); bw.BeginDeflateSection(); bw.WriteArray(data2); bw.EndDeflateSection(); bw.Dispose(); var file = bw.ToArray(); if (file.Length == 0) { TUtils.WriteFail($"FAIL file length is 0"); return(TestResult.Failure); } var br = new BinaryViewReader(file); br.BeginDeflateSection(); var rdata0 = br.ReadArray <byte>(); br.EndDeflateSection(); var rdata1 = br.ReadArray <byte>(); br.BeginDeflateSection(); var rdata2 = br.ReadArray <byte>(); br.EndDeflateSection(); br.Dispose(); if (!TUtils.IsIListEqual(data0, rdata0)) { TUtils.WriteFail($"FAIL data0: {TUtils.IListToString(rdata0)}, expected: {TUtils.IListToString(data0)}"); return(TestResult.Failure); } if (!TUtils.IsIListEqual(data1, rdata1)) { TUtils.WriteFail($"FAIL data1: {TUtils.IListToString(rdata1)}, expected: {TUtils.IListToString(data1)}"); return(TestResult.Failure); } if (!TUtils.IsIListEqual(data2, rdata2)) { TUtils.WriteFail($"FAIL data2: {TUtils.IListToString(rdata2)}, expected: {TUtils.IListToString(data2)}"); return(TestResult.Failure); } TUtils.WriteSucces($"OK"); return(TestResult.Success); }); }
private void ProcessTerrainCell(TreeSystemStructuredTrees cell, ref float treeDistSqr, ref float shadowDistSqr) { // Draw all trees instanced in MAX_BATCH chunks int tempIndexTree = 0; int tempIndexShadow = 0; float x, y, z; // If we are completely inside frustum we don't need to AABB test each tree bool insideFrustum = TUtils.IsCompletelyInsideFrustum(m_PlanesTemp, TUtils.BoundsCorners(ref cell.m_BoundsBox, ref m_TempCorners)); // If it is completely inside the frustum notify us if (insideFrustum) { m_DataIssuedTerrainCellsFull++; } // Tree instances TreeSystemStoredInstance[] treeInstances = cell.m_Instances; // TODO: Hm... if we take that hash it doesn't mean that it's the first visible one... int treeHash = treeInstances[0].m_TreeHash; int currentTreeHash = treeHash; int shadowHash = treeHash; int currentShadowHash = shadowHash; for (int treeIndex = 0; treeIndex < treeInstances.Length; treeIndex++) { // This is an order of magnitude faster than (treeInstances[treeIndex].m_WorldPosition - pos).sqrMagnitude // since it does not initiate with a ctor an extra vector during the computation x = treeInstances[treeIndex].m_WorldPosition.x - m_CameraPosTemp.x; y = treeInstances[treeIndex].m_WorldPosition.y - m_CameraPosTemp.y; z = treeInstances[treeIndex].m_WorldPosition.z - m_CameraPosTemp.z; float distToTree = x * x + y * y + z * z; if (insideFrustum) { // If we are completely inside the frustum we don't need to check each individual tree's bounds if (distToTree <= treeDistSqr) { currentTreeHash = treeInstances[treeIndex].m_TreeHash; if (tempIndexTree >= MAX_BATCH || treeHash != currentTreeHash) { // We're sure that they will not be shadows only IssueDrawTrees(m_ManagedPrototypesIndexed[treeHash], cell, m_IdxTempMesh, m_DstTempMesh, tempIndexTree, false); tempIndexTree = 0; // Update the hash treeHash = currentTreeHash; } m_IdxTempMesh[tempIndexTree] = treeIndex; m_DstTempMesh[tempIndexTree] = Mathf.Sqrt(distToTree); tempIndexTree++; m_DataIssuedMeshTrees++; } } else { // TODO: In the future instead of 'GeometryUtility.TestPlanesAABB' have our own function that // checks if the object is within the shadow volume, that is if it casts shadow inside our frustum // Just generate our own set of planes that we'll use... // If we are not completely inside the frustum we need to check the bounds of each individual tree if (distToTree <= treeDistSqr && GeometryUtility.TestPlanesAABB(m_PlanesTemp, treeInstances[treeIndex].m_WorldBounds)) { currentTreeHash = treeInstances[treeIndex].m_TreeHash; if (tempIndexTree >= MAX_BATCH || treeHash != currentTreeHash) { IssueDrawTrees(m_ManagedPrototypesIndexed[treeHash], cell, m_IdxTempMesh, m_DstTempMesh, tempIndexTree, false); tempIndexTree = 0; // Update the hash treeHash = currentTreeHash; } m_IdxTempMesh[tempIndexTree] = treeIndex; m_DstTempMesh[tempIndexTree] = Mathf.Sqrt(distToTree); tempIndexTree++; m_DataIssuedMeshTrees++; } // Same operation as above but with different matrices else if (m_Settings.m_ApplyShadowPoppingCorrection && distToTree < shadowDistSqr) { // Even if we are invisible but within the shadow sitance still cast shadows... // (not the most efficient method though, waiting for the complete one) currentShadowHash = treeInstances[treeIndex].m_TreeHash; if (tempIndexShadow >= MAX_BATCH || shadowHash != currentShadowHash) { IssueDrawTrees(m_ManagedPrototypesIndexed[shadowHash], cell, m_IdxTempShadow, m_DstTempShadow, tempIndexShadow, true); tempIndexShadow = 0; // Update the shadow hash shadowHash = currentShadowHash; } m_IdxTempShadow[tempIndexShadow] = treeIndex; m_DstTempShadow[tempIndexShadow] = Mathf.Sqrt(distToTree); tempIndexShadow++; m_DataIssuedShadows++; } } } // End cell tree iteration if (tempIndexTree > 0) { // Get a tree hash from the first element of the array so that we know for sure that we use the correct prototype data IssueDrawTrees(m_ManagedPrototypesIndexed[treeInstances[m_IdxTempMesh[0]].m_TreeHash], cell, m_IdxTempMesh, m_DstTempMesh, tempIndexTree, false); tempIndexTree = 0; } if (tempIndexShadow > 0) { IssueDrawTrees(m_ManagedPrototypesIndexed[treeInstances[m_IdxTempShadow[0]].m_TreeHash], cell, m_IdxTempShadow, m_DstTempShadow, tempIndexShadow, true); tempIndexShadow = 0; } }
private static void testSpeed() { var rnd = new Random(); var watch = new Stopwatch(); TUtils.Test("WriteByte x100000 time", () => { stream.Seek(0, SeekOrigin.Begin); watch.Restart(); for (int i = 0; i < 100000; i++) { bw.WriteByte((byte)rnd.NextDouble()); } watch.Stop(); TUtils.WriteSucces($"OK {watch.Elapsed.TotalMilliseconds}ms"); return(TestResult.Success); }); TUtils.Test("ReadByte x100000 time", () => { stream.Seek(0, SeekOrigin.Begin); watch.Restart(); for (int i = 0; i < 100000; i++) { br.ReadByte(); } watch.Stop(); TUtils.WriteSucces($"OK {watch.Elapsed.TotalMilliseconds}ms"); return(TestResult.Success); }); TUtils.Test("Write<byte> x100000 time", () => { stream.Seek(0, SeekOrigin.Begin); watch.Restart(); for (int i = 0; i < 100000; i++) { bw.Write <byte>((byte)rnd.NextDouble()); } watch.Stop(); TUtils.WriteSucces($"OK {watch.Elapsed.TotalMilliseconds}ms"); return(TestResult.Success); }); TUtils.Test("Read<byte> x100000 time", () => { stream.Seek(0, SeekOrigin.Begin); watch.Restart(); for (int i = 0; i < 100000; i++) { br.Read <byte>(); } watch.Stop(); TUtils.WriteSucces($"OK {watch.Elapsed.TotalMilliseconds}ms"); return(TestResult.Success); }); TUtils.Test("WriteDouble x100000 time", () => { stream.Seek(0, SeekOrigin.Begin); watch.Restart(); for (int i = 0; i < 100000; i++) { bw.WriteDouble(rnd.NextDouble()); } watch.Stop(); TUtils.WriteSucces($"OK {watch.Elapsed.TotalMilliseconds}ms"); return(TestResult.Success); }); TUtils.Test("ReadDouble x100000 time", () => { stream.Seek(0, SeekOrigin.Begin); watch.Restart(); for (int i = 0; i < 100000; i++) { br.ReadDouble(); } watch.Stop(); TUtils.WriteSucces($"OK {watch.Elapsed.TotalMilliseconds}ms"); return(TestResult.Success); }); TUtils.Test("Write<double> x100000 time", () => { stream.Seek(0, SeekOrigin.Begin); watch.Restart(); for (int i = 0; i < 100000; i++) { bw.Write <double>(rnd.NextDouble()); } watch.Stop(); TUtils.WriteSucces($"OK {watch.Elapsed.TotalMilliseconds}ms"); return(TestResult.Success); }); TUtils.Test("Read<double> x100000 time", () => { stream.Seek(0, SeekOrigin.Begin); watch.Restart(); for (int i = 0; i < 100000; i++) { br.Read <double>(); } watch.Stop(); TUtils.WriteSucces($"OK {watch.Elapsed.TotalMilliseconds}ms"); return(TestResult.Success); }); }
private void ProcessTerrainCell(TreeSystemStructuredTrees cell, ref float treeDistSqr) { // Draw all trees instanced in MAX_BATCH chunks int tempIndex = 0; float x, y, z; // If we are completely inside frustum we don't need to AABB test each tree bool insideFrustum = TUtils.IsCompletelyInsideFrustum(m_PlanesTemp, TUtils.BoundsCorners(ref cell.m_Bounds, ref m_TempCorners)); // Tree instances TreeSystemStoredInstance[] treeInstances = cell.m_Instances; // TODO: Hm... if we take that hash it doesn't mean that it's the first visible one... int treeHash = treeInstances[0].m_TreeHash; int currentTreeHash = treeHash; for (int treeIndex = 0; treeIndex < treeInstances.Length; treeIndex++) { // 1.33 ms for 110k trees // This is an order of magnitude faster than (treeInstances[treeIndex].m_WorldPosition - pos).sqrMagnitude // since it does not initiate with a ctor an extra vector during the computation x = treeInstances[treeIndex].m_WorldPosition.x - m_CameraPosTemp.x; y = treeInstances[treeIndex].m_WorldPosition.y - m_CameraPosTemp.y; z = treeInstances[treeIndex].m_WorldPosition.z - m_CameraPosTemp.z; float distToTree = x * x + y * y + z * z; // 17 ms for 110k trees // float distToTree = (treeInstances[treeIndex].m_WorldPosition - pos).sqrMagnitude; if (insideFrustum) { // If we are completely inside the frustum we don't need to check each individual tree's bounds if (distToTree <= treeDistSqr) { currentTreeHash = treeInstances[treeIndex].m_TreeHash; if (tempIndex >= MAX_BATCH || treeHash != currentTreeHash) { IssueDrawTrees(m_ManagedPrototypesIndexed[treeHash], cell, m_IdxTemp, m_DstTemp, tempIndex); tempIndex = 0; // Update the hash treeHash = currentTreeHash; } m_IdxTemp[tempIndex] = treeIndex; m_DstTemp[tempIndex] = Mathf.Sqrt(distToTree); tempIndex++; m_DataIssuedMeshTrees++; } } else { // If we are not completely inside the frustum we need to check the bounds of each individual tree if (distToTree <= treeDistSqr && GeometryUtility.TestPlanesAABB(m_PlanesTemp, treeInstances[treeIndex].m_WorldBounds)) { currentTreeHash = treeInstances[treeIndex].m_TreeHash; if (tempIndex >= MAX_BATCH || treeHash != currentTreeHash) { IssueDrawTrees(m_ManagedPrototypesIndexed[treeHash], cell, m_IdxTemp, m_DstTemp, tempIndex); tempIndex = 0; // Update the hash treeHash = currentTreeHash; } m_IdxTemp[tempIndex] = treeIndex; m_DstTemp[tempIndex] = Mathf.Sqrt(distToTree); tempIndex++; m_DataIssuedMeshTrees++; } } } // End cell tree iteration if (tempIndex > 0) { // Get a tree hash from the first element of the array so that we know for sure that we use the correc prototype data IssueDrawTrees(m_ManagedPrototypesIndexed[treeInstances[m_IdxTemp[0]].m_TreeHash], cell, m_IdxTemp, m_DstTemp, tempIndex); tempIndex = 0; } }