private void ShowNodeMerge(QuadNode <ChunkData> node, MeshData mesh, HashSet <QuadNode <ChunkData> > activeList) { if (!chunkMeshMap.ContainsKey(node)) { //Buffer CachedMeshHolder container = PopMeshContainer(); MeshFilter filter = container.filter; filter.sharedMesh = mesh.mesh; if (node.value.bounds == null) { node.value.bounds = new Sphere(Vector3.zero, 1); node.value.bounds.center = filter.sharedMesh.bounds.center; node.value.bounds.radius = Mathf.Sqrt( filter.sharedMesh.bounds.extents.x * filter.sharedMesh.bounds.extents.x + filter.sharedMesh.bounds.extents.y * filter.sharedMesh.bounds.extents.y + filter.sharedMesh.bounds.extents.z * filter.sharedMesh.bounds.extents.z ); } //Show node filter.gameObject.SetActive(true); //Call highest detail action if (node.depth == this.maxDepth) { //Call listeners if exists foreach (System.Action <QuadNode <ChunkData> > fn in this.listeners) { fn.Invoke(node); } //Call detailing service if available if (detailService != null) { detailService.ShowChunkDetails(node, filter.sharedMesh); } } //Add me if I don't already exist this.activeMeshes.Add(container); this.chunkMeshMap[node] = container; } if (!activeList.Contains(node)) { activeList.Add(node); } }
public PlanetFace(IMeshService ms, IDetailer ds, float baseRadius, int minDistance, int treeDepth, Range3d range, Material material) { //Apply params this.maxResolutionAt = minDistance; this.maxDepth = treeDepth; this.material = material; this.meshService = ms; this.detailService = ds; this.radius = baseRadius; //Create Gameobjects go = new GameObject("PlanetFace"); //Create quadtree root = new QuadNode <ChunkData>(range); GenerateChunkdata(root); }
/// <summary> /// Hides the chunk details. /// </summary> /// <param name="node">Node.</param> public override void HideChunkDetails(QuadNode <ChunkData> node) { //Stop active spawning session if it exists if (this.spawning.ContainsKey(node)) { Coroutine c = (this.spawning [node]); if (c != null) { StopCoroutine(this.spawning [node]); } this.spawning.Remove(node); } //This chunk has no objects associated to it List <GameObject> objects; if (!active.TryGetValue(node, out objects)) { return; } //Delete the object array from the active collection active.Remove(node); //This chunk needs to have its gameobjects either deleted or pooled foreach (GameObject obj in objects) { PlacementRule pool; if (!pools.TryGetValue(obj.name, out pool)) { //No pool GameObject.Destroy(obj); } else { //Pool exists obj.SetActive(false); obj.transform.localScale = Vector3.one; pool.pool.Enqueue(obj); } } }
/// <summary> /// Recursive check on this quadnode and its children. Test what LOD to load /// </summary> /// <param name="camera"></param> /// <param name="node"></param> private void ForceCheckLod(Vector3 camera, QuadNode <ChunkData> node) { //I need to go deeper DiscardNode(node); if ((node.isBranch || node.depth < maxDepth) && canSplit(camera, node)) { if (node.isLeaf) { Split(node); } ForceCheckLod(camera, node[Quadrant.NorthEast]); ForceCheckLod(camera, node[Quadrant.NorthWest]); ForceCheckLod(camera, node[Quadrant.SouthEast]); ForceCheckLod(camera, node[Quadrant.SouthWest]); } //Nope this is good (don't need to worry about merges on a forced method) else { //Show LOD ShowNode(node, this.activeChunks); } }
public override Material GetMaterialFor(PlanetFace face, QuadNode <ChunkData> node, Mesh mesh) { switch (face.direction) { case PlanetFaceDirection.Top: return(top); case PlanetFaceDirection.Bottom: return(bottom); case PlanetFaceDirection.Left: return(left); case PlanetFaceDirection.Right: return(right); case PlanetFaceDirection.Front: return(front); default: return(back); } }
private void DiscardNode(QuadNode <ChunkData> node) { CachedMeshHolder mf; if (chunkMeshMap.TryGetValue(node, out mf)) { //Hide and pool node this.activeMeshes.Remove(mf); this.meshPool.Enqueue(mf); mf.gameObject.SetActive(false); //Discard active node this.chunkMeshMap.Remove(node); //NOTE ** do not need to remove from activeChunks because the runtime algorithm resets this each frame //Call highest detail action if (node.depth == this.maxDepth) { //Call detailing service if available if (detailService != null) { detailService.HideChunkDetails(node); } } } }
/// <summary> /// Update LODs going up or down a single step only for runtime optimizations /// </summary> /// <param name="camera"></param> public void UpdateLODs(Vector3 camera) { //Loop Trough Active Nodes Deciding If We Should Keep Them Or Not HashSet <QuadNode <ChunkData> > ac = new HashSet <QuadNode <ChunkData> >(); foreach (QuadNode <ChunkData> node in this.activeChunks) { //Show Node if ((node.isBranch || node.depth < maxDepth) && canSplit(camera, node)) { if (node.isLeaf) { Split(node); } //If I already am generating this LOD if (splitTasks.ContainsKey(node)) { Task t = splitTasks[node]; if (t.state == TaskStatus.Complete) { //Can switch to child nodes DiscardNode(node); ShowNodeSplit(node, ((PlanetSplitTask)t).meshes, ac); splitTasks.Remove(node); } else { //Not yet, keep current ShowNode(node, ac); } } //If I am not yet generating this LOD, keep current else { PlanetSplitTask t = new PlanetSplitTask((s) => { PlanetSplitTask self = (PlanetSplitTask)s; //Generate Meshes for (int i = 0; i < 4; i++) { QuadNode <ChunkData> child = self.parent[(Quadrant)i]; MeshData m = meshService.Make( child.range.a, child.range.b, child.range.d, child.range.c, child.value.faceRegion, this.radius); self.meshes[i] = m; } }); t.parent = node; splitTasks[node] = t; TaskPool.EnqueueInvocation(t); ShowNode(node, ac); } } //Collapse Or Keep Node else { //Is Root, Cannot Split OR Child Wants To Merge But Parent Wants To Split if (node.isRoot || canSplit(camera, node.parent)) { ShowNode(node, ac); } //Child Wants To Merge And Parent Wants To Be Shown else { if (mergeTasks.ContainsKey(node.parent)) { //Generation already started Task t = mergeTasks[node.parent]; if (t.state == TaskStatus.Complete) { //Generation is complete, show parent DiscardNode(node.parent[Quadrant.NorthEast]); DiscardNode(node.parent[Quadrant.SouthEast]); DiscardNode(node.parent[Quadrant.NorthWest]); DiscardNode(node.parent[Quadrant.SouthWest]); ShowNodeMerge(node.parent, ((PlanetMergeTask)t).mesh, ac); mergeTasks.Remove(node.parent); } else { //Generation is not complete, keep node ShowNode(node, ac); } } else if (!ac.Contains(node.parent)) { //No generation started, keep node, start generation PlanetMergeTask t = new PlanetMergeTask((s) => { PlanetMergeTask self = (PlanetMergeTask)s; MeshData m = meshService.Make( self.node.range.a, self.node.range.b, self.node.range.d, self.node.range.c, self.node.value.faceRegion, this.radius); self.mesh = m; }); t.node = node.parent; mergeTasks[node.parent] = t; TaskPool.EnqueueInvocation(t); ShowNode(node, ac); } } } } //Set The Active Chunks this.activeChunks = ac; }
public abstract Material GetMaterialFor(PlanetFace face, QuadNode <ChunkData> node, Mesh mesh);
/// <summary> /// Hides the chunk details. /// </summary> /// <param name="node">Node.</param> public abstract void HideChunkDetails(QuadNode <ChunkData> node);
/// <summary> /// Shows the chunk details. /// </summary> /// <param name="node">Node.</param> public abstract void ShowChunkDetails(QuadNode <ChunkData> node, Mesh m);
public override Material GetMaterialFor(PlanetFace face, QuadNode <ChunkData> node, Mesh mesh) { return(material); }
/// <summary> /// Spawn a single object for the given rule /// </summary> /// <param name="planet"></param> /// <param name="stackDepth"></param> /// <param name="srcPool"></param> /// <param name="destinationPool"></param> /// <param name="node"></param> /// <param name="meshData"></param> public void SpawnObjectOnChunk(System.Random generator, Transform planet, int stackDepth, GameObjectPool srcPool, List <GameObject> destinationPool, QuadNode <ChunkData> node, Mesh meshData) { switch (placementType) { case RuleType.SurfaceRandom: SpawnSurfaceRandom(generator, planet, stackDepth, srcPool, destinationPool, node, meshData); break; case RuleType.SpecificCoordinate: SpawnSpecific(generator, planet, stackDepth, srcPool, destinationPool, node, meshData); break; } }
private void SpawnSurfaceRandom(System.Random generator, Transform planet, int stackDepth, GameObjectPool srcPool, List <GameObject> destinationPool, QuadNode <ChunkData> node, Mesh meshData) { Vector3[] verts = meshData.vertices; int[] tris = meshData.triangles; //Place int faceIdx = generator.Next(0, (tris.Length / 3) - 1) * 3; //Random x and y coordinates float rx = (float)generator.NextDouble(); float ry = (float)generator.NextDouble(); //Random position on face float sqrt_rx = Mathf.Sqrt(rx); Vector3 a = verts[tris[faceIdx]]; Vector3 b = verts[tris[faceIdx + 1]]; Vector3 c = verts[tris[faceIdx + 2]]; Vector3 pos = (1 - sqrt_rx) * a + (sqrt_rx * (1 - ry)) * b + (sqrt_rx * ry) * c; //Ignore if slope is too much float angle = Vector3.Angle(pos, Vector3.Cross(b - a, c - a)); if (angle > slopeLimit) { return; } //Ignore if not in altitude range float distance = pos.magnitude; if (distance < altitudeRange.low || distance > altitudeRange.high) { return; } //Determine scale Vector3 scale = Vector3.one * Random.Range(scaleRange.low, scaleRange.high); //Pool and spawn GameObject go = srcPool.Pop(); //Position object from pool go.transform.SetParent(planet); go.SetActive(true); go.transform.localScale = scale; go.transform.localPosition = pos; go.transform.localRotation = Quaternion.FromToRotation(Vector3.up, pos);// * Quaternion.Euler(0, 360 * Random.value, 0); //Add spawned object to reference list destinationPool.Add(go); }
private void SpawnSpecific(System.Random generator, Transform planet, int stackDepth, GameObjectPool srcPool, List <GameObject> destinationPool, QuadNode <ChunkData> node, Mesh meshData) { Vector3 worldCoordinates = SphericalToCartesian(new Vector3(2, theta, phi)); Plane p = new Plane(node.range.normal, node.range.center); Vector3 onPlane = p.ClosestPointOnPlane(worldCoordinates); if (PointInRectangle(onPlane, node.range.a, node.range.b, node.range.c, node.range.d)) { //Pool and spawn GameObject go = srcPool.Pop(); //Position object from pool go.transform.SetParent(planet); Vector3 pos = SphericalToCartesian(new Vector3(meshData.bounds.center.magnitude, theta, phi)); go.SetActive(true); go.transform.localScale = Vector3.one * fixedScale; go.transform.localPosition = pos; go.transform.localRotation = Quaternion.FromToRotation(Vector3.up, pos);// * Quaternion.Euler(0, 360 * Random.value, 0); //Add spawned object to reference list destinationPool.Add(go); } }
public int GetRandomSeed(int layer, QuadNode <ChunkData> node) { return(((seed + layer) << layer) * node.value.bounds.center.GetHashCode()); }
/// <summary> /// Split a quadnode and create the appropriate metadata /// </summary> /// <param name="parent"></param> private void Split(QuadNode <ChunkData> parent) { parent.Subdivide(); GenerateChildChunkdata(parent); }
private void ShowNode(QuadNode <ChunkData> node, HashSet <QuadNode <ChunkData> > activeList) { if (!chunkMeshMap.ContainsKey(node)) { //Buffer CachedMeshHolder container = PopMeshContainer(); MeshFilter filter = container.filter; container.collider.sharedMesh = filter.sharedMesh; container.collider.enabled = true; //Populate mesh filter.sharedMesh = meshService.Make( node.range.a, node.range.b, node.range.d, node.range.c, node.value.faceRegion, this.radius).mesh; //filter.sharedMesh = SubPlane.Make(node.range.a, node.range.b, node.range.d, node.range.c, resolution); container.renderer.sharedMaterial = textureService.GetMaterialFor(this, node, filter.sharedMesh); //Set chunk data if it was never computed before if (node.value.bounds == null) { node.value.bounds = new Sphere(Vector3.zero, 1); node.value.bounds.center = filter.sharedMesh.bounds.center; /*node.value.bounds.radius = Mathf.Max ( * filter.sharedMesh.bounds.extents.x, * filter.sharedMesh.bounds.extents.y, * filter.sharedMesh.bounds.extents.z * );*/ node.value.bounds.radius = Mathf.Sqrt( filter.sharedMesh.bounds.extents.x * filter.sharedMesh.bounds.extents.x + filter.sharedMesh.bounds.extents.y * filter.sharedMesh.bounds.extents.y + filter.sharedMesh.bounds.extents.z * filter.sharedMesh.bounds.extents.z ); } //Show node filter.gameObject.SetActive(true); //Call highest detail action if (node.depth == this.maxDepth) { //Call listeners if exists foreach (System.Action <QuadNode <ChunkData> > fn in this.listeners) { fn.Invoke(node); } //Call detailing service if available if (detailService != null) { detailService.ShowChunkDetails(node, filter.sharedMesh); } } //Add me if I don't already exist this.activeMeshes.Add(container); this.chunkMeshMap[node] = container; } if (!activeList.Contains(node)) { activeList.Add(node); } }
/// <summary> /// Spawn gameobejects for all rules on a given chunk. /// </summary> /// <param name="node">Node.</param> private IEnumerator SpawnForChunk(QuadNode <ChunkData> node, Mesh m, int amountToSpawnAtOnce) { //Get list of spawned objects for this node List <GameObject> spawned; if (active.ContainsKey(node)) { spawned = active [node]; } else { spawned = new List <GameObject> (); active [node] = spawned; } Vector3[] verts = m.vertices; int[] tris = m.triangles; //Go through all objects to place int j = 0; int k = 0; foreach (PlacementRule rule in objectsToPool) { //Bitshift ensures that successive rules with the same seeds will end up with different placements Random.InitState((rule.seed << j++) * node.value.bounds.center.GetHashCode()); //Number of this prefab to spawn int number = (int)Random.Range(rule.amountRange.low, rule.amountRange.high); float sl = rule.slopeLimit; for (int i = 0; i < number; i++) { int faceIdx = Random.Range(0, (tris.Length / 3) - 1) * 3; //Random x and y coordinates float rx = Random.value; float ry = Random.value; float sqrt_rx = Mathf.Sqrt(rx); Vector3 a = verts [tris[faceIdx]]; Vector3 b = verts [tris[faceIdx + 1]]; Vector3 c = verts [tris[faceIdx + 2]]; Vector3 pos = (1 - sqrt_rx) * a + (sqrt_rx * (1 - ry)) * b + (sqrt_rx * ry) * c; //Ignore if slope is too much float angle = Vector3.Angle(pos, Vector3.Cross(b - a, c - a)); if (angle > rule.slopeLimit) { continue; } //Ignore if not in altitude range float distance = pos.magnitude; if (distance < rule.altitudeRange.low || distance > rule.altitudeRange.high) { continue; } //Determine scale Vector3 scale = Vector3.one * Random.Range(rule.scaleRange.low, rule.scaleRange.high); //Pool and spawn if (rule.pool.Count < 1) { ExpandPool(rule, rule.poolBufferSize); } GameObject go = rule.pool.Dequeue(); go.SetActive(true); go.transform.localScale = scale; go.transform.localPosition = pos; go.transform.localRotation = Quaternion.FromToRotation(Vector3.up, pos); // * Quaternion.Euler(0, 360 * Random.value, 0); //Add spawned object to reference list spawned.Add(go); //If passed frame spawn limit, wait till next frame and reset the count k++; if (k >= amountToSpawnAtOnce) { k = 0; yield return(null); } } } }