public override List<RoadAtom> SpawnRoads(BranchAtom currentAtom, CityGenerator gen) { List<RoadAtom> production = new List<RoadAtom>(); if (currentAtom.Creator != null) { // Determine the vector from the creator atom's position and the current node's position, and split the // result into two groups: // - the vector is more aligned with the radius vector from the origin to the current node // - the vector is more aligned with the tangent of the circle which the radius vector defines Vector3 radiusVector = currentAtom.Node.position - gen.transform.position; Vector3 fromCreator = currentAtom.Node.position - currentAtom.Creator.Node.position; Vector3 tangent = Quaternion.Euler(0f, -90f, 0f) * radiusVector; Vector3 left = (Quaternion.Euler(0f, -90f, 0f) * fromCreator).normalized; Vector3 straight = fromCreator.normalized; Vector3 right = (Quaternion.Euler(0f, 90f, 0f) * fromCreator).normalized; // Get the angle between the radius vector and the vector from the creator to the current node // (Note: Vector3.Angle returns a value from 0f to 180f) float angle = Vector3.Angle(radiusVector, fromCreator); if (angle < 45f || angle > 135f) { // The incoming orientation is more aligned with the radius vector left = AlignVector(left, tangent); straight = AlignVector(straight, radiusVector); right = AlignVector(right, tangent); } else { // The incoming orientation is more aligned with the tangent left = AlignVector(left, radiusVector); straight = AlignVector(straight, tangent); right = AlignVector(right, radiusVector); } // For now, spawn roads in all three directions // TODO: Temporary production.Add(new RoadAtom(left, currentAtom.Node, Rule.Type.Radial)); production.Add(new RoadAtom(straight, currentAtom.Node, Rule.Type.Radial)); production.Add(new RoadAtom(right, currentAtom.Node, Rule.Type.Radial)); } else { // There's no creator (which means this is the axiom node), so we'll create roads shooting in all directions // TODO: Temporary production.Add(new RoadAtom(Vector3.forward, currentAtom.Node, Rule.Type.Radial)); production.Add(new RoadAtom(Vector3.back, currentAtom.Node, Rule.Type.Radial)); production.Add(new RoadAtom(Vector3.left, currentAtom.Node, Rule.Type.Radial)); production.Add(new RoadAtom(Vector3.right, currentAtom.Node, Rule.Type.Radial)); } return production; }
public override List<RoadAtom> SpawnRoads(BranchAtom currentAtom, CityGenerator gen) { List<RoadAtom> production = new List<RoadAtom>(); if (currentAtom.Creator != null) { // Get the orientation of the "parent" road (the one we're continuing in this production) MapNode toNode = currentAtom.Node; MapNode fromNode = currentAtom.Node.edges[0].FromNode; Vector3 parentDirection = (toNode.position - fromNode.position).normalized; // Get the elevation at the current point float currentElevation = gen.ElevationAt(currentAtom.Node.position); // Create three roads: left, right and straight with regard to the "parent" road's direction float[] angles = new float[] { -90f, 0f, 90f }; foreach (float angle in angles) { // Rotate the direction vector to get the direction we'll probe in Vector3 roadDirection = Quaternion.Euler(0f, angle, 0f) * parentDirection; // Probe elevations around the given direction and get the direction of the road which is least steep roadDirection = LeastSteepDirection(currentAtom.Node.position, roadDirection, currentElevation, gen); // Create a new RoadAtom with the given road direction RoadAtom roadAtom = new RoadAtom(roadDirection, currentAtom.Node, Rule.Type.Rectangular); // Add it to the production production.Add(roadAtom); } } else { // This is the axiom, just spawn roads in all directions production.Add(new RoadAtom(Vector3.forward, currentAtom.Node, Rule.Type.Rectangular)); production.Add(new RoadAtom(Vector3.back, currentAtom.Node, Rule.Type.Rectangular)); production.Add(new RoadAtom(Vector3.left, currentAtom.Node, Rule.Type.Rectangular)); production.Add(new RoadAtom(Vector3.right, currentAtom.Node, Rule.Type.Rectangular)); } return production; }
public override List<Atom> Produce(CityGenerator generator) { List<Atom> production = new List<Atom>(); // Create a new map node Rule rule = generator.RuleAtCoordinates(Node.position); MapNode spawn = new MapNode(Node.position + forward * rule.CalculateRoadLength(this, generator)); spawn.ruleType = ruleType; // Fetch the spawned node's neighbours List<MapNode> neighbours = generator.GetNeighbours(spawn, generator.neighboursSearchRadius); // Check if the node is close to one of its neighbours so that they can be merged into one node bool merged = false; Vector2 spawnPosition = spawn.PositionAsVector2; foreach (MapNode neighbour in neighbours) { // Convert coords to 2D, we don't want elevation messing around with our merging algorithm Vector2 neighbourPosition = neighbour.PositionAsVector2; // Check for proximity if (Vector2.Distance(spawnPosition, neighbourPosition) <= generator.nodeMergingMaximumDistance) { // The neighbour merges spawn = neighbour; // Stop iterating merged = true; break; } } // Create a map edge between the current map node and the spawn MapEdge spawnedEdge = new MapEdge(Node, spawn); if (!merged) { // Perform the intersection of the spawned node's edge against the neighbours' edges bool intersected = Intersect(spawn, spawnedEdge, neighbours); // Raycast to check for water RaycastHit hit; if (Physics.Raycast(spawn.position + Vector3.up * 1000f, Vector3.down, out hit)) { if (hit.collider.gameObject.tag == "Water") { // Spawned over water, remove the edge from the starting node Node.edges.Remove(spawnedEdge); // Skip this node return production; } // Set the Y coordinate of the spawned node to match the height where the raycast hit spawn.position.y = hit.point.y + 0.1f; } // Add the newly created map node to the environment generator.AddMapNode(spawn); // Continue producing only if there were no intersections if (!intersected) { // Summon a branch atom Atom branch = new BranchAtom(this); branch.Node = spawn; // Add the branch atom to the list of results and we're done production.Add(branch); } } return production; }
private void ProduceIfNecessary() { // Check if we're here for the first time if (currentGeneration == null) { // Initialize the list currentGeneration = new List<Atom>(); // Create the axiom // TODO: Add a configurable axiom Atom axiom = new BranchAtom(null); axiom.Node = new MapNode(transform.position); RootNode = axiom.Node; // Find out correct height for it RaycastHit hit; if (Physics.Raycast(transform.position + Vector3.up * 1000f, Vector3.down, out hit)) { axiom.Node.position.y = hit.point.y + 0.1f; } currentGeneration.Add(axiom); actualGenerations = 0; } // Check if we need to produce the L-system while (actualGenerations < targetGenerations) { Produce(); } }
/// <summary> /// Spawns the roads for the given branch atom. Each branch atom invokes this method during its production in order /// to calculate the roads which will be spawned from it. /// </summary> /// <returns>The roads.</returns> /// <param name="currentAtom">Current atom.</param> /// <param name="gen">City generator.</param> public abstract List<RoadAtom> SpawnRoads(BranchAtom currentAtom, CityGenerator gen);