public void Execute(int index) { GTreeInstance tree = instances[index]; Vector3 v = targetPos - tree.position; float sqrDistance = v.x * v.x + v.y * v.y + v.z * v.z; float sqrMaxDistance = maxDistance * maxDistance; cullResults[index] = sqrDistance <= sqrMaxDistance ? true : false; }
private static void RestoreTreeInstances(GStylizedTerrain t, string backupName) { string prototyeIndicesFileName = string.Format("{0}_{1}_{2}", t.TerrainData.Id, GBackupFile.TREE_SUFFIX, GBackupFile.PROTOTYPEINDEX_SUFFIX); string positionsFileName = string.Format("{0}_{1}_{2}", t.TerrainData.Id, GBackupFile.TREE_SUFFIX, GBackupFile.POSITION_SUFFIX); string rotationsFileName = string.Format("{0}_{1}_{2}", t.TerrainData.Id, GBackupFile.TREE_SUFFIX, GBackupFile.ROTATION_SUFFIX); string scalesFileName = string.Format("{0}_{1}_{2}", t.TerrainData.Id, GBackupFile.TREE_SUFFIX, GBackupFile.SCALE_SUFFIX); byte[] protoIndicesData = GBackupFile.ReadAllBytes(backupName, prototyeIndicesFileName); byte[] positionsData = GBackupFile.ReadAllBytes(backupName, positionsFileName); byte[] rotationData = GBackupFile.ReadAllBytes(backupName, rotationsFileName); byte[] scalesData = GBackupFile.ReadAllBytes(backupName, scalesFileName); if (protoIndicesData != null && positionsData != null && rotationData != null && scalesData != null) { protoIndicesData = GCompressor.Decompress(protoIndicesData); positionsData = GCompressor.Decompress(positionsData); rotationData = GCompressor.Decompress(rotationData); scalesData = GCompressor.Decompress(scalesData); int[] indices = new int[protoIndicesData.Length / sizeof(int)]; float[] positions = new float[positionsData.Length / sizeof(float)]; float[] rotations = new float[rotationData.Length / sizeof(float)]; float[] scales = new float[scalesData.Length / sizeof(float)]; Buffer.BlockCopy(protoIndicesData, 0, indices, 0, protoIndicesData.Length); Buffer.BlockCopy(positionsData, 0, positions, 0, positionsData.Length); Buffer.BlockCopy(rotationData, 0, rotations, 0, rotationData.Length); Buffer.BlockCopy(scalesData, 0, scales, 0, scalesData.Length); List <GTreeInstance> trees = new List <GTreeInstance>(); for (int i = 0; i < indices.Length; ++i) { GTreeInstance tree = GTreeInstance.Create(indices[i]); tree.Position = new Vector3( positions[i * 3 + 0], positions[i * 3 + 1], positions[i * 3 + 2]); tree.Rotation = new Quaternion( rotations[i * 4 + 0], rotations[i * 4 + 1], rotations[i * 4 + 2], rotations[i * 4 + 3]); tree.Scale = new Vector3( scales[i * 3 + 0], scales[i * 3 + 1], scales[i * 3 + 2]); trees.Add(tree); } t.TerrainData.Foliage.TreeInstances.Clear(); t.TerrainData.Foliage.TreeInstances.AddRange(trees); t.TerrainData.SetDirty(GTerrainData.DirtyFlags.Foliage); } }
private void DoImportTrees() { if (!ImportTreeInstancesOnly) { GTreePrototypeGroup treeGroup = DesData.Foliage.Trees; if (treeGroup == null || treeGroup == GRuntimeSettings.Instance.foliageDefault.trees) { CreateNewTreePrototypesGroup = true; } if (CreateNewTreePrototypesGroup) { treeGroup = ScriptableObject.CreateInstance <GTreePrototypeGroup>(); #if UNITY_EDITOR if (!Application.isPlaying) { string path = AssetDatabase.GetAssetPath(DesData); string directory = Path.GetDirectoryName(path); string filePath = Path.Combine(directory, string.Format("Trees_{0}_{1}.asset", DesData.Id, System.DateTime.Now.Ticks)); AssetDatabase.CreateAsset(treeGroup, filePath); } #endif DesData.Foliage.Trees = treeGroup; } treeGroup.Prototypes.Clear(); TreePrototype[] treePrototypes = SrcData.treePrototypes; for (int i = 0; i < treePrototypes.Length; ++i) { GTreePrototype proto = (GTreePrototype)treePrototypes[i]; treeGroup.Prototypes.Add(proto); } GCommon.SetDirty(treeGroup); } DesData.Foliage.TreeInstances.Clear(); TreeInstance[] treeInstances = SrcData.treeInstances; for (int i = 0; i < treeInstances.Length; ++i) { GTreeInstance t = (GTreeInstance)treeInstances[i]; DesData.Foliage.TreeInstances.Add(t); } if (DesTerrain != null) { DesData.Foliage.SetTreeRegionDirty(GCommon.UnitRect); DesTerrain.UpdateTreesPosition(); DesData.Foliage.ClearTreeDirtyRegions(); } DesData.SetDirty(GTerrainData.DirtyFlags.Foliage); //GC.Collect(); }
private void SpawnTreesOnTerrain(GStylizedTerrain t, Color[] maskData, List <Vector4> vertices) { int treeIndex = -1; Vector2 v0 = Vector2.zero; Vector2 v1 = Vector2.zero; Vector2 v2 = Vector2.zero; Vector2 center = Vector2.zero; float radius = 0; Vector2 pos = Vector2.zero; Vector3 bary = Vector3.zero; float maskValue = 0; int trisCount = vertices.Count / 3; for (int i = 0; i < trisCount; ++i) { v0 = t.WorldPointToUV(vertices[i * 3 + 0]); v1 = t.WorldPointToUV(vertices[i * 3 + 1]); v2 = t.WorldPointToUV(vertices[i * 3 + 2]); center = (v0 + v1 + v2) / 3; radius = Vector2.Distance(center, v0); for (int s = 0; s < TreeDensity; ++s) { treeIndex = TreePrototypeIndices[Random.Range(0, TreePrototypeIndices.Count)]; pos = center + Random.insideUnitCircle * radius; if (pos.x < 0 || pos.x > 1 || pos.y < 0 || pos.x > 1) { continue; } GUtilities.CalculateBarycentricCoord(pos, v0, v1, v2, ref bary); if (bary.x < 0 || bary.y < 0 || bary.z < 0) { continue; } maskValue = GUtilities.GetColorBilinear(maskData, MaskResolution, MaskResolution, pos).r; if (Random.value > maskValue) { continue; } GTreeInstance tree = GTreeInstance.Create(treeIndex); tree.Position = new Vector3(pos.x, 0, pos.y); tree.Rotation = Quaternion.Euler(0, Random.Range(MinRotation, MaxRotation), 0); tree.Scale = Vector3.Lerp(MinScale, MaxScale, Random.value); t.TerrainData.Foliage.TreeInstances.Add(tree); } } }
public void Execute(int index) { GTreeInstance tree = instances[index]; Vector3 pos = new Vector3( tree.position.x * terrainSize.x, tree.position.y * terrainSize.y, tree.position.z * terrainSize.z); tree.position = pos; instances[index] = tree; }
private void LateUpdate() { if (Terrain == null) { return; } if (Terrain.TerrainData == null) { return; } if (Terrain.TerrainData.Foliage.Trees == null) { return; } if (Terrain.TerrainData.Foliage.Trees.Prototypes.Count == 0) { return; } if (treeInstances == null || treeInstances.Length == 0) { return; } GameObject actualTarget = null; if (Target != null) { actualTarget = Target; } else if (Camera.main != null) { actualTarget = Camera.main.gameObject; } if (actualTarget == null) { return; } Vector3 targetLocalPos = Terrain.transform.InverseTransformPoint(actualTarget.transform.position); GTreeColliderCullJob job = new GTreeColliderCullJob() { instances = nativeTreeInstances, cullResults = nativeCullResults, maxDistance = distance, targetPos = targetLocalPos }; JobHandle handle = job.Schedule(nativeTreeInstances.Length, 100); handle.Complete(); if (cullResults == null || cullResults.Length != nativeCullResults.Length) { cullResults = new bool[nativeCullResults.Length]; } nativeCullResults.CopyTo(cullResults); List <GTreePrototype> prototypes = Terrain.TerrainData.Foliage.Trees.Prototypes; int colliderIndex = 0; Vector3 terrainPos = Terrain.transform.position; Vector3 worldPos = Vector3.zero; if (terrain.TerrainData.Rendering.DrawTrees) { for (int i = 0; i < treeInstances.Length; ++i) { if (cullResults[i] == false) { continue; } GTreeInstance tree = treeInstances[i]; GTreePrototype prototype = prototypes[tree.prototypeIndex]; if (prototype.prefab == null) { continue; } if (!prototype.hasCollider) { continue; } CapsuleCollider col = GetCollider(colliderIndex); colliderIndex += 1; worldPos.Set( tree.position.x + terrainPos.x, tree.position.y + terrainPos.y, tree.position.z + terrainPos.z); col.transform.position = worldPos; col.transform.rotation = tree.rotation; col.transform.localScale = tree.scale; GTreeColliderInfo colliderInfo = prototype.colliderInfo; col.center = colliderInfo.center; col.radius = colliderInfo.radius; col.height = colliderInfo.height; col.direction = colliderInfo.direction; col.gameObject.layer = prototype.layer; if (CopyTreeTag) { col.gameObject.tag = prototype.prefab.tag; } col.gameObject.SetActive(true); } } int colliderCount = Colliders.Count; for (int i = colliderIndex; i < colliderCount; ++i) { CapsuleCollider col = GetCollider(i); col.gameObject.SetActive(false); } }
private void SpawnTreeOnTerrain(GStylizedTerrain t, Color[] maskData, int layerIndex) { GFoliageStampLayer layer = Layers[layerIndex]; Vector3 centerPos = Vector3.zero; Vector3 samplePos = Vector3.zero; Vector2 uv = Vector2.zero; float maskValue = 0; Vector3 terrainSize = new Vector3( t.TerrainData.Geometry.Width, t.TerrainData.Geometry.Height, t.TerrainData.Geometry.Length); Vector3 scale = new Vector3( GUtilities.InverseLerpUnclamped(0, terrainSize.x, Scale.x), 1, GUtilities.InverseLerpUnclamped(0, terrainSize.z, Scale.z)); Matrix4x4 matrix = Matrix4x4.TRS( t.WorldPointToNormalized(Position), Rotation, scale); int treeIndex = -1; int instanceCount = 0; int attempt = 0; int maxAttempt = layer.TreeInstanceCount * 100; #if UNITY_EDITOR string title = "Stamping on " + t.name; string info = string.Format("Layer: {0}", !string.IsNullOrEmpty(layer.Name) ? layer.Name : layerIndex.ToString()); int currentPercent = 0; int attemptPercent = 0; int instancePercent = 0; GCommonGUI.CancelableProgressBar(title, info, 0); #endif while (instanceCount < layer.TreeInstanceCount && attempt <= maxAttempt) { attempt += 1; #if UNITY_EDITOR attemptPercent = (int)(attempt * 100.0f / maxAttempt); instancePercent = (int)(instanceCount * 100.0f / layer.TreeInstanceCount); if (currentPercent != Mathf.Max(attemptPercent, instancePercent)) { currentPercent = Mathf.Max(attemptPercent, instancePercent); GCommonGUI.CancelableProgressBar(title, string.Format("{0} ... {1}%", info, currentPercent), currentPercent / 100.0f); } #endif treeIndex = layer.TreeIndices[Random.Range(0, layer.TreeIndices.Count)]; centerPos.Set(Random.value - 0.5f, 0, Random.value - 0.5f); samplePos = matrix.MultiplyPoint(centerPos); if (samplePos.x < 0 || samplePos.x > 1 || samplePos.z < 0 || samplePos.z > 1) { continue; } uv.Set(samplePos.x, samplePos.z); maskValue = GUtilities.GetColorBilinear(maskData, MaskResolution, MaskResolution, uv).r; if (Random.value > maskValue) { continue; } GTreeInstance tree = GTreeInstance.Create(treeIndex); tree.Position = new Vector3(samplePos.x, 0, samplePos.z); tree.Rotation = Quaternion.Euler(0, Random.Range(layer.MinRotation, layer.MaxRotation), 0); tree.Scale = Vector3.Lerp(layer.MinScale, layer.MaxScale, Random.value); t.TerrainData.Foliage.TreeInstances.Add(tree); instanceCount += 1; } #if UNITY_EDITOR GCommonGUI.ClearProgressBar(); #endif }
private void HandleSpawnTree(GStylizedTerrain terrain, GFoliagePainterArgs args) { int treeIndex = -1; Vector3 randomPos = Vector3.zero; Vector3 rayOrigin = Vector3.zero; Vector3 rayDirection = Vector3.down; float sqrtTwo = Mathf.Sqrt(2); Ray ray = new Ray(); RaycastHit samplePoint; Vector3 bary0 = Vector3.zero; Vector3 bary1 = Vector3.zero; Vector2 maskUv = Vector2.zero; Vector2 samplePointTexcoord = Vector2.zero; Color maskColor = Color.white; Texture2D clonedMask = null; Texture2D terrainMask = null; if (args.Mask != null) { clonedMask = GCommon.CloneAndResizeTexture(args.Mask, 256, 256); } if (args.EnableTerrainMask) { terrainMask = terrain.TerrainData.Mask.MaskMap; } int prototypeCount = terrain.TerrainData.Foliage.Trees.Prototypes.Count; List <GTreeInstance> newInstances = new List <GTreeInstance>(); for (int i = 0; i < args.Density; ++i) { treeIndex = args.TreeIndices[Random.Range(0, args.TreeIndices.Count)]; if (treeIndex < 0 || treeIndex >= prototypeCount) { continue; } randomPos = args.HitPoint + Random.insideUnitSphere * args.Radius * sqrtTwo; rayOrigin.Set( randomPos.x, 10000, randomPos.z); ray.origin = rayOrigin; ray.direction = rayDirection; if (terrain.Raycast(ray, out samplePoint, float.MaxValue)) { GUtilities.CalculateBarycentricCoord( new Vector2(samplePoint.point.x, samplePoint.point.z), new Vector2(args.WorldPointCorners[0].x, args.WorldPointCorners[0].z), new Vector2(args.WorldPointCorners[1].x, args.WorldPointCorners[1].z), new Vector2(args.WorldPointCorners[2].x, args.WorldPointCorners[2].z), ref bary0); GUtilities.CalculateBarycentricCoord( new Vector2(samplePoint.point.x, samplePoint.point.z), new Vector2(args.WorldPointCorners[0].x, args.WorldPointCorners[0].z), new Vector2(args.WorldPointCorners[2].x, args.WorldPointCorners[2].z), new Vector2(args.WorldPointCorners[3].x, args.WorldPointCorners[3].z), ref bary1); if (bary0.x >= 0 && bary0.y >= 0 && bary0.z >= 0) { maskUv = bary0.x * Vector2.zero + bary0.y * Vector2.up + bary0.z * Vector2.one; } else if (bary1.x >= 0 && bary1.y >= 0 && bary1.z >= 0) { maskUv = bary1.x * Vector2.zero + bary1.y * Vector2.one + bary1.z * Vector2.right; } else { continue; } //sample mask if (clonedMask != null) { maskColor = clonedMask.GetPixelBilinear(maskUv.x, maskUv.y); if (Random.value > maskColor.grayscale) { continue; } } //sample terrain mask if (args.EnableTerrainMask) { samplePointTexcoord = samplePoint.textureCoord; maskColor = terrainMask.GetPixelBilinear(samplePointTexcoord.x, samplePointTexcoord.y); if (Random.value < maskColor.r) { continue; } } //apply filter GSpawnFilterArgs filterArgs = GSpawnFilterArgs.Create(); filterArgs.Terrain = terrain; filterArgs.Position = samplePoint.point; filterArgs.SurfaceNormal = samplePoint.normal; filterArgs.SurfaceTexcoord = samplePoint.textureCoord; List <Type> suitableFilter = SuitableFilterTypes; if (args.Filters != null) { for (int fIndex = 0; fIndex < args.Filters.Length; ++fIndex) { if (args.Filters[fIndex] != null && args.Filters[fIndex].Ignore != true) { if (suitableFilter.Contains(args.Filters[fIndex].GetType())) { args.Filters[fIndex].Apply(ref filterArgs); } } if (filterArgs.ShouldExclude) { break; } } } //spawn if (filterArgs.ShouldExclude) { continue; } GTreeInstance tree = GTreeInstance.Create(treeIndex); tree.Position = terrain.WorldPointToNormalized(filterArgs.Position); tree.Rotation = filterArgs.Rotation; tree.Scale = filterArgs.Scale; newInstances.Add(tree); } } terrain.TerrainData.Foliage.AddTreeInstances(newInstances); newInstances.Clear(); if (clonedMask != null) { Object.DestroyImmediate(clonedMask); } }
public void Paint(Pinwheel.Griffin.GStylizedTerrain terrain, GFoliagePainterArgs args) { if (args.TreeIndices.Count == 0) { return; } if (terrain.TerrainData == null) { return; } if (terrain.TerrainData.Foliage.Trees == null) { return; } if (args.MouseEventType == GPainterMouseEventType.Up || args.ShouldCommitNow) { terrain.UpdateTreesPosition(); terrain.TerrainData.Foliage.ClearTreeDirtyRegions(); GCommon.SetDirty(terrain.TerrainData.Foliage); return; } Vector2[] uvCorners = new Vector2[args.WorldPointCorners.Length]; for (int i = 0; i < uvCorners.Length; ++i) { uvCorners[i] = terrain.WorldPointToUV(args.WorldPointCorners[i]); } Rect dirtyRect = GUtilities.GetRectContainsPoints(uvCorners); if (!dirtyRect.Overlaps(new Rect(0, 0, 1, 1))) { return; } Texture2D clonedMask = null; if (args.Mask != null) { clonedMask = GCommon.CloneAndResizeTexture(args.Mask, 256, 256); } int multiplier = args.ActionType == GPainterActionType.Normal ? 1 : -1; int treeIndex = -1; Vector3 terrainSize = new Vector3( terrain.TerrainData.Geometry.Width, terrain.TerrainData.Geometry.Height, terrain.TerrainData.Geometry.Length); Vector3 localPos = Vector3.zero; Vector3 worldPos = Vector3.zero; Vector3 bary0 = Vector3.zero; Vector3 bary1 = Vector3.zero; Vector2 maskUv = Vector2.zero; Color maskColor = Color.white; Vector3 scale = Vector3.zero; List <GTreeInstance> instances = terrain.TerrainData.Foliage.TreeInstances; int instanceCount = instances.Count; for (int i = 0; i < instanceCount; ++i) { treeIndex = args.TreeIndices[Random.Range(0, args.TreeIndices.Count)]; GTreeInstance tree = instances[i]; if (tree.PrototypeIndex != treeIndex) { continue; } localPos.Set( tree.Position.x * terrainSize.x, tree.Position.y * terrainSize.y, tree.Position.z * terrainSize.z); worldPos = terrain.transform.TransformPoint(localPos); GUtilities.CalculateBarycentricCoord( new Vector2(worldPos.x, worldPos.z), new Vector2(args.WorldPointCorners[0].x, args.WorldPointCorners[0].z), new Vector2(args.WorldPointCorners[1].x, args.WorldPointCorners[1].z), new Vector2(args.WorldPointCorners[2].x, args.WorldPointCorners[2].z), ref bary0); GUtilities.CalculateBarycentricCoord( new Vector2(worldPos.x, worldPos.z), new Vector2(args.WorldPointCorners[0].x, args.WorldPointCorners[0].z), new Vector2(args.WorldPointCorners[2].x, args.WorldPointCorners[2].z), new Vector2(args.WorldPointCorners[3].x, args.WorldPointCorners[3].z), ref bary1); if (bary0.x >= 0 && bary0.y >= 0 && bary0.z >= 0) { maskUv = bary0.x * Vector2.zero + bary0.y * Vector2.up + bary0.z * Vector2.one; } else if (bary1.x >= 0 && bary1.y >= 0 && bary1.z >= 0) { maskUv = bary1.x * Vector2.zero + bary1.y * Vector2.one + bary1.z * Vector2.right; } else { continue; } if (clonedMask != null) { maskColor = clonedMask.GetPixelBilinear(maskUv.x, maskUv.y); if (Random.value > maskColor.grayscale) { continue; } } scale.Set( Mathf.Max(0, tree.Scale.x + multiplier * maskColor.grayscale * args.ScaleStrength * GUtilities.DELTA_TIME), Mathf.Max(0, tree.Scale.y + multiplier * maskColor.grayscale * args.ScaleStrength * GUtilities.DELTA_TIME), Mathf.Max(0, tree.Scale.z + multiplier * maskColor.grayscale * args.ScaleStrength * GUtilities.DELTA_TIME)); GSpawnFilterArgs filterArgs = GSpawnFilterArgs.Create(); filterArgs.Terrain = terrain; filterArgs.Position = worldPos; filterArgs.Rotation = tree.Rotation; filterArgs.Scale = scale; List <Type> suitableFilter = SuitableFilterTypes; if (args.Filters != null) { for (int fIndex = 0; fIndex < args.Filters.Length; ++fIndex) { if (args.Filters[fIndex] != null && args.Filters[fIndex].Ignore != true) { if (suitableFilter.Contains(args.Filters[fIndex].GetType())) { args.Filters[fIndex].Apply(ref filterArgs); } } if (filterArgs.ShouldExclude) { break; } } } tree.Scale = filterArgs.Scale; instances[i] = tree; } terrain.TerrainData.Foliage.SetTreeRegionDirty(dirtyRect); terrain.TerrainData.SetDirty(GTerrainData.DirtyFlags.Foliage); GUtilities.MarkCurrentSceneDirty(); }
private static void BackupTreeInstances(GStylizedTerrain t, string backupName) { if (t.TerrainData.Foliage.Trees == null) { return; } List <GTreeInstance> trees = t.TerrainData.Foliage.TreeInstances; int[] protoIndices = new int[trees.Count]; float[] positions = new float[trees.Count * 3]; float[] rotations = new float[trees.Count * 4]; float[] scales = new float[trees.Count * 3]; for (int i = 0; i < trees.Count; ++i) { GTreeInstance tree = trees[i]; protoIndices[i] = tree.PrototypeIndex; positions[i * 3 + 0] = tree.Position.x; positions[i * 3 + 1] = tree.Position.y; positions[i * 3 + 2] = tree.Position.z; rotations[i * 4 + 0] = tree.Rotation.x; rotations[i * 4 + 1] = tree.Rotation.y; rotations[i * 4 + 2] = tree.Rotation.z; rotations[i * 4 + 3] = tree.Rotation.w; scales[i * 3 + 0] = tree.Scale.x; scales[i * 3 + 1] = tree.Scale.y; scales[i * 3 + 2] = tree.Scale.z; } byte[] protoIndicesData = new byte[Buffer.ByteLength(protoIndices)]; Buffer.BlockCopy(protoIndices, 0, protoIndicesData, 0, protoIndicesData.Length); protoIndicesData = GCompressor.Compress(protoIndicesData); byte[] positionsData = new byte[Buffer.ByteLength(positions)]; Buffer.BlockCopy(positions, 0, positionsData, 0, positionsData.Length); positionsData = GCompressor.Compress(positionsData); byte[] rotationsData = new byte[Buffer.ByteLength(rotations)]; Buffer.BlockCopy(rotations, 0, rotationsData, 0, rotationsData.Length); rotationsData = GCompressor.Compress(rotationsData); byte[] scalesData = new byte[Buffer.ByteLength(scales)]; Buffer.BlockCopy(scales, 0, scalesData, 0, scalesData.Length); scalesData = GCompressor.Compress(scalesData); GBackupFile.Create( backupName, string.Format("{0}_{1}_{2}", t.TerrainData.Id, GBackupFile.TREE_SUFFIX, GBackupFile.PROTOTYPEINDEX_SUFFIX), protoIndicesData); GBackupFile.Create( backupName, string.Format("{0}_{1}_{2}", t.TerrainData.Id, GBackupFile.TREE_SUFFIX, GBackupFile.POSITION_SUFFIX), positionsData); GBackupFile.Create( backupName, string.Format("{0}_{1}_{2}", t.TerrainData.Id, GBackupFile.TREE_SUFFIX, GBackupFile.ROTATION_SUFFIX), rotationsData); GBackupFile.Create( backupName, string.Format("{0}_{1}_{2}", t.TerrainData.Id, GBackupFile.TREE_SUFFIX, GBackupFile.SCALE_SUFFIX), scalesData); }
public void Execute(int index) { GTreeInstance tree = instances[index]; if (tree.prototypeIndex < 0 || tree.prototypeIndex >= prototypePivotOffset.Length) { cullResult[index] = flagCulled; return; } if (tree.position.x < cullBoxMin.x || tree.position.x > cullBoxMax.x || tree.position.y < cullBoxMin.y || tree.position.y > cullBoxMax.y || tree.position.z < cullBoxMin.z || tree.position.z > cullBoxMax.z) { cullResult[index] = flagCulled; return; } float pivotOffset = prototypePivotOffset[tree.prototypeIndex]; Vector3 worldPos = new Vector3( tree.position.x * terrainSize.x + terrainPos.x, tree.position.y * terrainSize.y + terrainPos.y + pivotOffset, tree.position.z * terrainSize.z + terrainPos.z); float sqrDistance = Vector3.SqrMagnitude(worldPos - cameraPos); float sqrTreeDistance = treeDistance * treeDistance; if (sqrDistance > sqrTreeDistance) { cullResult[index] = flagCulled; return; } Vector3 baseScale = prototypeBaseScale[tree.prototypeIndex]; Vector3 worldScale = new Vector3( tree.scale.x * baseScale.x, tree.scale.y * baseScale.y, tree.scale.z * baseScale.z); bool testFrustum = prototypeWillDoFrustumTest[tree.prototypeIndex]; if (testFrustum) { BoundingSphere b = prototypeBounds[tree.prototypeIndex]; b.position = worldPos; b.radius *= Mathf.Max(worldScale.x, Mathf.Max(worldScale.y, worldScale.z)); b.radius += cullVolumeBias; if (!DoFrustumTest(frustum, b)) { cullResult[index] = flagCulled; return; } } float sqrBillboardStart = billboardStart * billboardStart; if (sqrDistance >= sqrBillboardStart) { cullResult[index] = flagBillboard; } else { cullResult[index] = flagVisible; } if (prototypeIndices[index] < 0) { Quaternion baseRotation = prototypeBaseRotation[tree.prototypeIndex]; Quaternion worldRotation = tree.rotation * baseRotation; Matrix4x4 matrix = Matrix4x4.TRS(worldPos, worldRotation, worldScale); transforms[index] = matrix; prototypeIndices[index] = tree.prototypeIndex; } }