private void CreateBiomeMaskArea() { // create new gameobject GameObject go = new GameObject("Biome Mask Area"); // add this component go.AddComponent <BiomeMaskAreaExtension>(); // modify created biome BiomeMaskArea mask = go.GetComponent <BiomeMaskArea>(); mask.BiomeType = extension.biomeSettings.biomeType; // TODO: raycast terrain // position it to the center of the viewport SceneView.lastActiveSceneView.MoveToView(go.transform); // TODO: center of screen, recreate nodes // ensure gameobject gets reparented if this was a context click (otherwise does nothing) GameObjectUtility.SetParentAndAlign(go, extension.transform.gameObject); // reparent gameobject //go.transform.SetParent(extension.transform.gameObject.transform); // register the creation in the undo system Undo.RegisterCreatedObjectUndo(go, "Create " + go.name); }
/// <summary> /// Insert a new node between every node segment of the area /// </summary> public static void Subdivide(BiomeMaskArea mask) { List <Node> originalNodes = new List <Node>(); originalNodes.AddRange(mask.Nodes); for (var i = 0; i < originalNodes.Count; i++) { Node curr = originalNodes[i]; Node next = mask.GetNextNode(curr); Vector3[] segment = new Vector3[] { curr.Position, next.Position }; Vector3 meanVector = PolygonUtils.GetMeanVector(segment); int index = mask.GetNodeIndex(curr); Node newNode = new Node() { Position = meanVector }; mask.Nodes.Insert(index + 1, newNode); } UpdateMask(mask); }
/// <summary> /// Get a position and angle along the biome mask's edge. /// </summary> /// <param name="position"></param> /// <param name="angle"></param> private void GetRandomBiomeEdgePosition(out Vector3 position, out float angle) { BiomeMaskArea mask = editor.extension.lineSettings.biomeMaskArea; // parameter consistency check if (mask == null) { Debug.LogError("No mask defined"); position = Vector3.zero; angle = 0; return; } List <Vector3> positions = BiomeMaskUtils.GetPositions(mask); // sort clockwise, so that the pick algorithm works // if this were counterclockwise, then the angle would make the lines face inwards PolygonUtils.SortClockWise(positions); // get from node index int nodeIndexFrom = Random.Range(0, positions.Count); // note: int is exclusive last // get to node index, consider overlap int nodeIndexTo = nodeIndexFrom + 1; if (nodeIndexTo >= mask.Nodes.Count) { nodeIndexTo = 0; } // get nodes Vector3 positionFrom = mask.transform.position + positions[nodeIndexFrom]; Vector3 positionTo = mask.transform.position + positions[nodeIndexTo]; // having the lines flip inwards into the biome is just a matter of changing the access order of the nodes // leaving this here, maybe we find a use case later bool flipAngle = editor.extension.lineSettings.attachedAngleFlip; if (flipAngle) { Vector3 tmp = positionFrom; positionFrom = positionTo; positionTo = tmp; } float distance = (positionTo - positionFrom).magnitude; Vector3 direction = (positionTo - positionFrom).normalized; // the position along the edge. 0=from, 0.5=center, 1=to float relativePosition = Random.Range(0f, 1f); // calculate the position position = positionFrom + direction * distance * relativePosition; // calculate the angle 90 degrees to the from-to points and convert to degrees angle = Mathf.Atan2(positionTo.z - positionFrom.z, positionTo.x - positionFrom.x) * Mathf.Rad2Deg; }
/// <summary> /// Force update on the mask in scene view /// </summary> /// <param name="mask"></param> public static void UpdateMask(BiomeMaskArea mask) { // TODO: that's just a quick hack to update the mask. mask.PositionNodes(); doesn't seem to work and mask.Update() has an optimization in it mask.transform.position = mask.transform.position + new Vector3(0, 1, 0); mask.transform.position = mask.transform.position + new Vector3(0, -1, 0); // apply the mask to the vegetation mask.UpdateBiomeMask(); }
public void OnInspectorGUI() { EditorGUILayout.Space(); EditorGUILayout.LabelField("Terrain Processing", GUIStyles.GroupTitleStyle); EditorGUILayout.PropertyField(partitionAlgorithm, new GUIContent("Algorithm", "The algorithm to use for terrain partitioning.")); EditorGUILayout.PropertyField(terrainProcessing, new GUIContent("Bounds", "Process all terrains as a single combined terrain or all terrains individually.")); BoundsProcessing selectedTerrainProcessing = (BoundsProcessing)System.Enum.GetValues(typeof(BoundsProcessing)).GetValue(terrainProcessing.enumValueIndex); if (selectedTerrainProcessing == BoundsProcessing.Biome) { EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(boundsBiomeMaskArea, new GUIContent("Biome Mask", "The Biome used for clipping.")); // check if the changed biome mask is convex if (EditorGUI.EndChangeCheck() || editor.performInitialConsistencyCheck) { if (boundsBiomeMaskArea.objectReferenceValue != null) { boundsBiomeMaskAreaValid.boolValue = false; BiomeMaskArea biomeMaskArea = (BiomeMaskArea)boundsBiomeMaskArea.objectReferenceValue; Vector2[] clipPolygon = editor.GetBiomeClipPolygon(biomeMaskArea); if (clipPolygon != null) { // consistency check: clip polygon must be convex for sutherland hodgman bool isConvex = PolygonUtils.PolygonIsConvex(clipPolygon); if (isConvex) { boundsBiomeMaskAreaValid.boolValue = true; } else { Debug.LogError("Invalid clipping mask: " + biomeMaskArea.name + " (" + biomeMaskArea.MaskName + ")"); } } } } // show error in case the mask doesn't exist if (boundsBiomeMaskArea.objectReferenceValue == null) { EditorGUILayout.HelpBox("The Biome Mask must be defined!", MessageType.Error); } // show error in case the mask isn't convex else if (!boundsBiomeMaskAreaValid.boolValue) { EditorGUILayout.HelpBox("The Biome Mask must be convex!", MessageType.Error); } } }
/// <summary> /// Clear the nodes of the mask and set the provided ones. /// </summary> /// <param name="mask"></param> /// <param name="nodes"></param> private void SetMaskNodes(BiomeMaskArea mask, List <Vector3> nodes) { mask.ClearNodes(); foreach (Vector3 node in nodes) { mask.AddNodeToEnd(node); } mask.PositionNodes(); }
public static void CreateHexagon(BiomeMaskArea mask) { float radius = GetRadius(mask); Vector3[] hexagon = ShapeCreator.CreateHexagon(mask.transform.position, radius); mask.Nodes.Clear(); mask.AddNodesToEnd(hexagon); // center main handle, implicitly updates the mask CenterMainHandle(mask); }
public void CreateBiomeMaskArea(string gameObjectName, string maskName, Vector3 position, List <Vector3> nodes, float blendDistance) { GameObject parentGameObject = extension.transform.gameObject; // create new gameobject GameObject biomeGameObject = new GameObject(gameObjectName); // add this component biomeGameObject.AddComponent <BiomeMaskAreaExtension>(); // ensure gameobject gets reparented if this was a context click (otherwise does nothing) GameObjectUtility.SetParentAndAlign(biomeGameObject, parentGameObject); // set position biomeGameObject.transform.position = position; // that's actually not necessary, we call CenterMainHandle after the mask nodes were created // modify created biome BiomeMaskArea mask = biomeGameObject.GetComponent <BiomeMaskArea>(); mask.BiomeType = extension.biomeSettings.biomeType; // blend distance mask.BlendDistance = blendDistance; // create nodes mask.MaskName = maskName; // grow/shrink mask nodes = PolygonUtils.Resize(nodes, extension.shapeSettings.resizeFactor); // random shape inside bounds SetMaskNodes(mask, nodes); // put move handle into the center of the polygon BiomeMaskUtils.CenterMainHandle(mask); #region Lake Polygon // create a lake and re-parent the biome to it if (extension.lakeSettings.createLake) { lakeModule.CreateLake(mask, gameObjectName, nodes); } #endregion Lake Polygon // tegister the creation in the undo system Undo.RegisterCreatedObjectUndo(biomeGameObject, "Create " + biomeGameObject.name); }
/// <summary> /// Resize the mask /// </summary> /// <param name="factor">Positive values for growing, negative for shrinking. 0.1 means grow by 10%, -0.1 means shrink by 10%</param> public static void ResizeMask(BiomeMaskArea mask, float factor) { Vector3 center = GetMaskCenter(mask); foreach (Node node in mask.Nodes) { Vector3 worldPos = mask.transform.TransformPoint(node.Position); Vector3 distance = worldPos - center; worldPos += distance * factor; node.Position = mask.transform.InverseTransformPoint(worldPos); } UpdateMask(mask); }
/// <summary> /// Transform the mask and convert it into its convex hull. /// </summary> /// <param name="mask"></param> public static void ConvexHull(BiomeMaskArea mask) { Vector3 maskPosition = mask.transform.position; List <Vector2> positionsXY = mask.Nodes.ConvertAll <Vector2>(item => new Vector2(item.Position.x, item.Position.z)); List <Vector2> convexHull = PolygonUtility.GetConvexHull(positionsXY); mask.Nodes.Clear(); foreach (Vector2 nodePosition in convexHull) { mask.AddNodeToEnd(new Vector3(maskPosition.x + nodePosition.x, 0, maskPosition.z + nodePosition.y)); } // center main handle, implicitly updates the mask CenterMainHandle(mask); }
public static float GetRadius(BiomeMaskArea mask) { Vector3 maskCenter = GetMaskCenter(mask); List <Vector3> worldPositions = mask.GetWorldSpaceNodePositions(); // calculate the radius by using the average distance from the mask center float magnitudeSum = 0f; foreach (Vector3 worldPosition in worldPositions) { magnitudeSum += (worldPosition - maskCenter).magnitude; } float radius = magnitudeSum / mask.Nodes.Count; return(radius); }
/// <summary> /// If bounds clip setting is set to biome, get the clip polygon from it /// </summary> /// <returns></returns> public Vector2[] GetBiomeClipPolygon(BiomeMaskArea biomeMaskArea) { if (biomeMaskArea == null) { return(null); } float biomePositionX = biomeMaskArea.transform.position.x; float biomePositionZ = biomeMaskArea.transform.position.z; Vector2[] clipPolygon = biomeMaskArea.Nodes.ConvertAll <Vector2>( item => new Vector2( biomePositionX + item.Position.x, biomePositionZ + item.Position.z )).ToArray(); return(clipPolygon); }
/// <summary> /// Move the main handle into the center of the maks polygon /// </summary> /// <param name="mask"></param> public static void CenterMainHandle(BiomeMaskArea mask) { Vector3 center = GetMaskCenter(mask); Vector3 offset = mask.transform.position - center; mask.transform.position = center; foreach (AwesomeTechnologies.VegetationSystem.Biomes.Node node in mask.Nodes) { Vector3 worldPos = mask.transform.TransformPoint(node.Position); worldPos += offset; node.Position = mask.transform.InverseTransformPoint(worldPos); } UpdateMask(mask); }
/// <summary> /// Remove every 2nd node /// </summary> public static void Unsubdivide(BiomeMaskArea mask) { // ensure there is at least the specified number of nodes left int minimumNodeCount = 3; if (mask.Nodes.Count <= minimumNodeCount) { return; } int count = mask.Nodes.Count; for (var i = mask.Nodes.Count - 1; i >= 0; i -= 2) { mask.Nodes.RemoveAt(i); if (mask.Nodes.Count < minimumNodeCount) { break; } } UpdateMask(mask); }
/// <summary> /// Create a lake and re-parent the mask so that the lake becomes the child of the container and the mask the child of the lake. /// /// Note: the lake uses a bezier curve on the points, so the lake most likely won't be 100% in line with the mask. /// </summary> /// <param name="maskGameObject"></param> /// <param name="gameObjectName"></param> /// <param name="nodes"></param> public static LakePolygon CreateLakePolygon(LakeSettings lakeSettings, BiomeMaskArea mask, GameObject maskGameObject, string gameObjectName, List <Vector3> nodes) { GameObject maskParentGameObject = maskGameObject.transform.parent.gameObject; Vector3 maskPosition = maskGameObject.transform.position; // lake LakePolygon lakePolygon = LakePolygon.CreatePolygon(AssetDatabase.GetBuiltinExtraResource <Material>("Default-Diffuse.mat")); // apply profile lakePolygon.currentProfile = lakeSettings.lakeProfile; // apply gameobject data lakePolygon.transform.localPosition = Vector3.zero; lakePolygon.name = "Lake " + gameObjectName; // add biome nodes if (lakeSettings.ramInternalLakeCreation) { // apply settings lakePolygon.angleSimulation = lakeSettings.angleSimulation; lakePolygon.closeDistanceSimulation = lakeSettings.closeDistanceSimulation; lakePolygon.checkDistanceSimulation = lakeSettings.checkDistanceSimulation; lakePolygon.removeFirstPointSimulation = lakeSettings.removeFirstPointSimulation; // add point lakePolygon.AddPoint(maskPosition); // start simulation lakePolygon.Simulation(); } // use mask shape else { foreach (Vector3 node in nodes) { lakePolygon.AddPoint(maskParentGameObject.transform.InverseTransformPoint(node)); } } // generate the lake lakePolygon.GeneratePolygon(); // re-parent the mask to the lake and the lake to the parent GameObjectUtility.SetParentAndAlign(lakePolygon.gameObject, maskParentGameObject); GameObjectUtility.SetParentAndAlign(maskGameObject, lakePolygon.gameObject); // adjust the lake position lakePolygon.transform.position = maskPosition; // reset the mask position, it's now a child of the lake if (lakeSettings.ramInternalLakeCreation) { maskGameObject.transform.position = Vector3.zero; List <Vector3> newPoints = new List <Vector3>(); foreach (Vector3 node in lakePolygon.points) { newPoints.Add(lakePolygon.transform.TransformPoint(node)); } // set the lake's polygon as new mask nodes SetMaskNodes(mask, newPoints); // put move handle into the center of the polygon BiomeMaskUtils.CenterMainHandle(mask); } // re-apply the material so that the water becomes immediately visible MeshRenderer meshRenderer = lakePolygon.GetComponent <MeshRenderer>(); meshRenderer.sharedMaterial = lakePolygon.currentProfile.lakeMaterial; /* carving is disabled for now * if ( extension.lakeSettings.carveTerrain) * { * Debug.Log("Start carve"); * lakePolygon.terrainSmoothMultiplier = 2f; * lakePolygon.distSmooth = 10f; * lakePolygon.TerrainCarve(); * } */ // EditorUtility.SetDirty(lakePolygon); return(lakePolygon); }
public void Awake() { extension = (BiomeMaskAreaExtension)target; mask = extension.GetComponent <BiomeMaskArea>(); }
/// <summary> /// Shrink the mask by the given factor /// </summary> /// <param name="mask"></param> /// <param name="factor"></param> public static void Shrink(BiomeMaskArea mask, float factor) { ResizeMask(mask, -Mathf.Abs(factor)); }
/// <summary> /// Grow the mask by the given factor /// </summary> /// <param name="mask"></param> /// <param name="factor"></param> public static void Grow(BiomeMaskArea mask, float factor) { ResizeMask(mask, Mathf.Abs(factor)); }
/// <summary> /// Get the center of the mask polygon /// </summary> /// <param name="mask"></param> /// <returns></returns> public static Vector3 GetMaskCenter(BiomeMaskArea mask) { List <Vector3> worldPositions = mask.GetWorldSpaceNodePositions(); return(PolygonUtils.GetMeanVector(worldPositions.ToArray())); }
/// <summary> /// Create a list of node positions. /// </summary> /// <param name="mask"></param> /// <returns></returns> public static List <Vector3> GetPositions(BiomeMaskArea mask) { return(mask.Nodes.ConvertAll <Vector3>(item => new Vector3(item.Position.x, item.Position.y, item.Position.z))); }
public void CreateLake(BiomeMaskArea mask, string gameObjectName, List <Vector3> nodes) { #if RAM_2019 LakePolygon lakePolygon = RamLakeCreator.CreateLakePolygon(editor.extension.lakeSettings, mask, mask.transform.gameObject, gameObjectName, nodes); #endif }