public void RemoveAndDestroyElement(DecorPaintObjectPlacementBrushElement element) { if (ContainsElement(element) && element != null) { _elements.Remove(element); ClampActiveElementIndex(); UndoEx.DestroyObjectImmediate(element); } }
public DecorPaintObjectPlacementBrushElement CreateNewElement() { DecorPaintObjectPlacementBrushElement newElement = Octave3DWorldBuilder.ActiveInstance.CreateScriptableObject <DecorPaintObjectPlacementBrushElement>(); newElement.ParentBrush = this; _elements.Add(newElement); if (_activeElementIndex < 0) { SetActiveElement(newElement); } return(newElement); }
public void SetActiveElement(DecorPaintObjectPlacementBrushElement newActiveElement) { if (newActiveElement == null) { _activeElementIndex = -1; } else { int newActiveIndex = _elements.FindIndex(item => item == newActiveElement); if (newActiveIndex >= 0) { _activeElementIndex = newActiveIndex; } } }
protected override void PerformDrop() { if (_dropDestination == DropDestination.Element) { if (_destinationDecorPaintBrushElement == null) { return; } List <GameObject> validUnityPrefabsInvolvedInDropOperation = PrefabValidator.GetValidPrefabsFromEntityCollection(DragAndDrop.objectReferences, false); if (validUnityPrefabsInvolvedInDropOperation.Count != 0) { PerformDropUsingFirstPrefabInValidUnityPrefabCollection(validUnityPrefabsInvolvedInDropOperation); } } else { if (_destinationBrush == null) { return; } List <GameObject> validUnityPrefabs = PrefabValidator.GetValidPrefabsFromEntityCollection(DragAndDrop.objectReferences, false); if (validUnityPrefabs.Count != 0) { UndoEx.RecordForToolAction(_destinationBrush); foreach (GameObject unityPrefab in validUnityPrefabs) { DecorPaintObjectPlacementBrushElement newElement = _destinationBrush.CreateNewElement(); PrefabCategory categoryWhichContainsPrefab = PrefabCategoryDatabase.Get().GetPrefabCategoryWhichContainsPrefab(unityPrefab); if (categoryWhichContainsPrefab != null) { newElement.Prefab = categoryWhichContainsPrefab.GetPrefabByUnityPrefab(unityPrefab); } else { Prefab prefab = PrefabFactory.Create(unityPrefab); UndoEx.RecordForToolAction(_destinationBrush.DestinationCategoryForElementPrefabs); PrefabWithPrefabCategoryAssociationQueue.Instance.Enqueue(PrefabWithPrefabCategoryAssociationFactory.Create(prefab, _destinationBrush.DestinationCategoryForElementPrefabs)); newElement.Prefab = prefab; } } } } Octave3DWorldBuilder.ActiveInstance.RepaintAllEditorWindows(); }
private void AdjustObjectBoxCenterToSitOnSurface(OrientedBox objectBox, ObjectSurfaceData objectSurfaceData, DecorPaintObjectPlacementBrushElement brushElement) { if (brushElement.AlignToSurface) { objectBox.Center = objectSurfaceData.BasePosition + objectSurfaceData.SurfaceNormal * 0.5f * objectBox.GetSizeAlongDirection(objectSurfaceData.SurfaceNormal); } else { BoxFace boxFaceWhichFacesSurfaceNormal = objectBox.GetBoxFaceWhichFacesNormal(_workingBrushCircle.Plane.normal); Vector3 faceCenter = objectBox.GetBoxFaceCenter(boxFaceWhichFacesSurfaceNormal); Vector3 fromFaceCenterToBoxCenter = objectBox.Center - faceCenter; objectBox.Center = objectSurfaceData.BasePosition + fromFaceCenterToBoxCenter; } }
private void AdjustObjectBoxRotationOnSurface(OrientedBox objectBox, ObjectSurfaceData objectSurfaceData, DecorPaintObjectPlacementBrushElement brushElement) { if (brushElement.AlignToSurface) { objectBox.Rotation = AxisAlignment.CalculateRotationQuaternionForAxisAlignment(objectBox.Rotation, brushElement.AlignmentAxis, objectSurfaceData.SurfaceNormal); } }
private MatrixObjectBoxPair CalculateMatrixObjectBoxPair(int brushElementIndex, ObjectSurfaceData objectSurfaceData) { // Store the needed data for easy access DecorPaintObjectPlacementBrushElement brushElement = _allValidBrushElements[brushElementIndex]; OrientedBox objectBox = new OrientedBox(_brushElementsPrefabOrientedBoxes[brushElementIndex]); // Establish the object's scale. Randomize the scale if necessary. Otherwise, just use the brush element's scale. Vector3 xyzObjectBoxScale = _brushElementsPrefabWorldScaleValues[brushElementIndex]; if (brushElement.ScaleRandomizationSettings.RandomizeScale) { // Generate a random scale factor and apply it to the current scale ObjectUniformScaleRandomizationSettings uniformScaleRandomizationSettings = brushElement.ScaleRandomizationSettings.UniformScaleRandomizationSettings; xyzObjectBoxScale *= UnityEngine.Random.Range(uniformScaleRandomizationSettings.MinScale, uniformScaleRandomizationSettings.MaxScale);; } else { xyzObjectBoxScale *= brushElement.Scale; } // Apply the scale that we have calculated objectBox.Scale = xyzObjectBoxScale; // Now we will calculate the rotation. First we will calculate a quaternion which allow us to take the // specified rotation offset into account. The quaternion rotates around the surface normal by an angle // of 'RotationOffset' degrees. Quaternion rotationOffset = brushElement.AlignToSurface ? Quaternion.AngleAxis(brushElement.RotationOffsetInDegrees, objectSurfaceData.SurfaceNormal) : Quaternion.identity; // If we need to align to stroke, we have some more work to do. Otherwise, we will just set the // rotation to be the same as the prefab's rotation, but offset by 'rotationOffset'. if (brushElement.AlignToStroke) { // First calculate the rotation without any offset. This is the rotation of the prefab plus the rotation which // must be applied for alignment. Quaternion rotationWithoutOffset = _rotationToApplyForStrokeAlignment * _elementToCurrentPrefabRotation[brushElement]; // The rotation of the box must be set to the rotation which we just calculated (which ensures proper alingment) plus // the rotation offset. objectBox.Rotation = rotationOffset * rotationWithoutOffset; // Store the rotation inside the dictionary if an entry doesn't already exist if (!_elementToNewPrefabRotation.ContainsKey(brushElement)) { _elementToNewPrefabRotation.Add(brushElement, rotationWithoutOffset); } } else { objectBox.Rotation = rotationOffset * brushElement.Prefab.UnityPrefab.transform.rotation; } // Adjust the rotation of the object so that its axis is aligned with the placement surface if necessary if (brushElement.AlignToSurface) { AdjustObjectBoxRotationOnSurface(objectBox, objectSurfaceData, brushElement); } // The final step is to rotate the object by a random amount around the placement surface if (brushElement.RotationRandomizationMode != BrushElementRotationRandomizationMode.None) { Vector3 rotationAxis = objectSurfaceData.SurfaceNormal; if (brushElement.RotationRandomizationMode == BrushElementRotationRandomizationMode.X) { rotationAxis = Vector3.right; } else if (brushElement.RotationRandomizationMode == BrushElementRotationRandomizationMode.Y) { rotationAxis = Vector3.up; } else if (brushElement.RotationRandomizationMode == BrushElementRotationRandomizationMode.Z) { rotationAxis = Vector3.forward; } if (!brushElement.AlignToStroke) { // When stroke alignment is not required, we will generate a random rotation angle and apply it // to the current box rotation. float randomRotationAngle = UnityEngine.Random.Range(0.0f, 360.0f); Quaternion additionalRotation = Quaternion.AngleAxis(randomRotationAngle, rotationAxis); objectBox.Rotation = additionalRotation * objectBox.Rotation; } else { // When both rotation randomization and stroke alingment are turned on, we will proudce a small // random rotation offset to randomize the alingmnet a little bit. float randomRotationAngle = UnityEngine.Random.Range(0.0f, 25.0f); Quaternion additionalRotation = Quaternion.AngleAxis(randomRotationAngle, rotationAxis); objectBox.Rotation = additionalRotation * objectBox.Rotation; } } // Place the object on the surface AdjustObjectBoxCenterToSitOnSurface(objectBox, objectSurfaceData, brushElement); // Construct the object matrix and return the object/marix pair TransformMatrix objectMatrix = new TransformMatrix(ObjectPositionCalculator.CalculateObjectHierarchyPosition(brushElement.Prefab, objectBox.Center, xyzObjectBoxScale, objectBox.Rotation), objectBox.Rotation, xyzObjectBoxScale); return(new MatrixObjectBoxPair(objectMatrix, objectBox)); }
private bool DoesObjectSurfacePassSlopeTest(ObjectSurfaceData objectSurfaceData, DecorPaintObjectPlacementBrushElement brushElement) { if (!_workingBrushCircle.IsSittingOnTerrain && brushElement.SlopeSettings.UseSlopeOnlyForTerrainObjects) { return(true); } return(brushElement.SlopeSettings.IsNormalInSlopeRange(objectSurfaceData.SurfaceNormal)); }
public List <ObjectPlacementData> Calculate(Quaternion rotationToApplyForStrokeAlignment) { if (!ValidateCalculationRequirements()) { return(new List <ObjectPlacementData>()); } _objectNodeNetwork.Clear(); _rotationToApplyForStrokeAlignment = rotationToApplyForStrokeAlignment; CreateSurfaceColliderProjector(); _elementToNewPrefabRotation.Clear(); _allowObjectIntersection = ObjectPlacementSettings.Get().ObjectIntersectionSettings.AllowIntersectionForDecorPaintBrushModeDrag; int currentObjectIndex = 0; var objectPlacementDataInstances = new List <ObjectPlacementData>(_workingBrush.MaxNumberOfObjects); while (currentObjectIndex < _workingBrush.MaxNumberOfObjects) { DecorPaintObjectPlacementBrushElement brushElement = _brushElementSpawnChanceTable.PickEntity(UnityEngine.Random.Range(0.0f, 1.0f)); int brushElementIndex = _allValidBrushElements.FindIndex(item => item == brushElement); ++currentObjectIndex; // No object nodes were created yet? if (_objectNodeNetwork.NumberOfNodes == 0) { // Create the first node at a random position inside the brush circle Vector3 randomPositionInsideCircle = _workingBrushCircle.GetRandomPointInside(); ObjectSurfaceData objectSurfaceData = CalculateObjectSurfaceData(randomPositionInsideCircle); MatrixObjectBoxPair matrixObjectBoxPair = CalculateMatrixObjectBoxPair(brushElementIndex, objectSurfaceData); // We need to know if the normal of the surface on which the object resides lies within the desired slope range bool passesSlopeTest = DoesObjectSurfacePassSlopeTest(objectSurfaceData, brushElement); // Note: Even if the slope test is not passed, we will still create an object node. The reason for this is that // we want to have some kind of continuity in the algorithm. Imagine that the brush circle is large and is // divided by a large terrain mountain which sits in the middle. If the object generation starts on one side // of the mountain, the algorithm might never get a chance to go over the other side if the slope condition // is not satisifed. We want to spread objects as much as possible so even though this object will not be // placed in the scene, we will still add it to the node network. _objectNodeNetwork.AddNodeToEnd(matrixObjectBoxPair.ObjectBox, objectSurfaceData); if (passesSlopeTest && DoesBoxPassObjectIntersectionTest(matrixObjectBoxPair.ObjectBox, brushElement.Prefab.UnityPrefab, matrixObjectBoxPair.ObjectMatrix)) { objectPlacementDataInstances.Add(new ObjectPlacementData(matrixObjectBoxPair.ObjectMatrix, brushElement.Prefab, brushElement.MustEmbedInSurface)); } } else { // Are there any node segments available? if (_objectNodeNetwork.NumberOfSegments != 0) { // The first step is to generate a random node index and store references to that node and its immediate neighbour _objectNodeNetwork.RemoveAllNodesWhichGenerateConcavities(_workingBrushCircle.Plane); int randomNodeIndex = _objectNodeNetwork.GetRandomNodeIndex(); ObjectNode firstNode = _objectNodeNetwork.GetNodeByIndex(randomNodeIndex); ObjectNode secondNode = _objectNodeNetwork.GetNodeByIndex(randomNodeIndex + 1); // Calculate the plane of the segment which unites the 2 nodes. We will also store the // actual segment and the middle point on the segment. We will use this middle point to // generate the initial object position. Segment3D nodeSegment = ObjectNodeNetwork.CalculateSegmentBetweenNodes(firstNode, secondNode); Vector3 segmentMidPoint = nodeSegment.GetPoint(0.5f); Plane segmentPlane = ObjectNodeNetwork.CalculateSegmentPlaneNormal(firstNode, secondNode); OrientedBox firstNodeBox = firstNode.ObjectBox; OrientedBox secondNodeBox = secondNode.ObjectBox; // Calculate the furthest point in front of the plane using the corner points of the // 2 nodes. The idea is to move the new object as much as possible from the bulk of // objects that have already been generated. Vector3 furthestPointFromPlane; List <Vector3> nodeCornerPoints = firstNodeBox.GetCornerPoints(); nodeCornerPoints.AddRange(secondNodeBox.GetCornerPoints()); if (!segmentPlane.GetFurthestPointInFront(nodeCornerPoints, out furthestPointFromPlane)) { continue; } // Use the calculated furthest point from plane and the the existing plane normal to calculate the // pivot plane. The new object will reside at some distance away from this plane. Plane pivotPlane = new Plane(segmentPlane.normal, furthestPointFromPlane); // Calculate the new object transform data. We will use the segment's mid point to generate the // initial object position. ObjectSurfaceData objectSurfaceData = CalculateObjectSurfaceData(segmentMidPoint); MatrixObjectBoxPair matrixObjectBoxPair = CalculateMatrixObjectBoxPair(brushElementIndex, objectSurfaceData); OrientedBox objectBox = matrixObjectBoxPair.ObjectBox; // Identify the objects's furthest point behind the plane Vector3 objectBoxPivotPoint; List <Vector3> objectBoxCornerPoints = objectBox.GetCornerPoints(); if (!pivotPlane.GetFurthestPointBehind(objectBoxCornerPoints, out objectBoxPivotPoint)) { continue; } // Use the furthest point to move the object in front of the plane and take the distance between objects into account Vector3 fromPivotPointToCenter = objectBox.Center - objectBoxPivotPoint; Vector3 projectedPivotPoint = pivotPlane.ProjectPoint(objectBoxPivotPoint); objectBox.Center = projectedPivotPoint + fromPivotPointToCenter + pivotPlane.normal * _workingBrush.DistanceBetweenObjects; // Generate the object surface data objectSurfaceData = CalculateObjectSurfaceData(objectBox.Center); bool passesSlopeTest = DoesObjectSurfacePassSlopeTest(objectSurfaceData, brushElement); // Now we need to adjust the orientation and center of the box. If the calculated center // lies outside the brush circle, we will ignore this node. AdjustObjectBoxRotationOnSurface(objectBox, objectSurfaceData, brushElement); AdjustObjectBoxCenterToSitOnSurface(objectBox, objectSurfaceData, brushElement); if (!_workingBrushCircle.ContainsPoint(_workingBrushCircle.Plane.ProjectPoint(objectBox.Center))) { continue; } // Recalculate the object matrix using the new box data TransformMatrix objectMatrix = matrixObjectBoxPair.ObjectMatrix; objectMatrix.Rotation = objectBox.Rotation; objectMatrix.Translation = ObjectPositionCalculator.CalculateObjectHierarchyPosition(brushElement.Prefab, objectBox.Center, objectMatrix.Scale, objectBox.Rotation); // We have been modifying the matrix and box data independently so we will ensure that the box uses the latest data OrientedBox finalBox = new OrientedBox(objectBox); finalBox.SetTransformMatrix(objectMatrix); // If the slope test passed, we will calculate an object placement data instance. Otherwise, we will just insert a new node. if (passesSlopeTest && DoesBoxPassObjectIntersectionTest(finalBox, brushElement.Prefab.UnityPrefab, objectMatrix)) { objectPlacementDataInstances.Add(new ObjectPlacementData(objectMatrix, brushElement.Prefab, brushElement.MustEmbedInSurface)); } _objectNodeNetwork.InsertAfterNode(objectBox, objectSurfaceData, randomNodeIndex); } else { // When there are no segments available it means we have only one node. We will use this node to generate // a new one at some distance away from it. First we will store some data that we will need during the entire // procedure. ObjectNode pivotNode = _objectNodeNetwork.GetFirstNode(); Vector3 pivotNodeSurfaceTangent = pivotNode.ObjectSurfaceData.GetSurfaceTangentVector(); OrientedBox pivotNodeObjectBox = pivotNode.ObjectBox; // We will place the new node at some distance away from the first node's face which points // along the calculated tangent vector. We will call this the pivot face. BoxFace pivotBoxFace = pivotNodeObjectBox.GetBoxFaceMostAlignedWithNormal(pivotNodeSurfaceTangent); Plane pivotFacePlane = pivotNodeObjectBox.GetBoxFacePlane(pivotBoxFace); // Generate the data for the new node in the same position as the first node. // Note: Although the same position is used, the rotation and scale will differ and they will // be established by 'CalculateMatrixObjectBoxPair'. MatrixObjectBoxPair matrixObjectBoxPair = CalculateMatrixObjectBoxPair(brushElementIndex, pivotNode.ObjectSurfaceData); OrientedBox objectBox = matrixObjectBoxPair.ObjectBox; // At this point we have to start moving the generated object box to its new positino along the // tangent vector. We will do this by calculating the furthest box point which lies behind the // pivot plane and then move the box so that this point resides on that plane. We will call this // the pivot point. // Note: We will perform a safety check to see if this point could not be calculated and use the // closest point in front if necessary. However, this check should not be necessary. Because // we are placing te object box in the center of the previous box, we can be usre that there // will always be a point which lies behind the pivot plane. Vector3 objectBoxPivotPoint; List <Vector3> objectBoxCornerPoints = objectBox.GetCornerPoints(); if (!pivotFacePlane.GetFurthestPointBehind(objectBoxCornerPoints, out objectBoxPivotPoint) && !pivotFacePlane.GetClosestPointInFront(objectBoxCornerPoints, out objectBoxPivotPoint)) { continue; } // Project the pivot point onto the pivot plane. We will also store a vector which goes from the // original pivot point to the box center. This will allow us to maintain the relationship between // the projected pivot point and the box center so that the center can be adjusted accordingly. Vector3 fromPivotPointToCenter = objectBox.Center - objectBoxPivotPoint; Vector3 projectedPivotPoint = pivotFacePlane.ProjectPoint(objectBoxPivotPoint); // Adjust the center using the projected pivot point and also take the distance between objects into account objectBox.Center = projectedPivotPoint + fromPivotPointToCenter + pivotNodeSurfaceTangent * _workingBrush.DistanceBetweenObjects; // Generate the object surface data at the current box position. // Note: This is the step which can actually cause objects to intersect a little bit. The surface data is // calculated by projecting along the brush circle plane normal. If we are placing objects on a terrain // and the center of the circle lies somewhere at the base of the terrain where the normal points straight // up, but the center of the box resides somewhere on a clif, the new center might move the box closer // or even further away from the pivot node. This however, should not be a problem especially if the distance // between objects is not 0. ObjectSurfaceData objectSurfaceData = CalculateObjectSurfaceData(objectBox.Center); bool passesSlopeTest = DoesObjectSurfacePassSlopeTest(objectSurfaceData, brushElement); // Now we need to adjust the orientation and center of the box. If the calculated center // lies outside the brush circle, we will ignore this node. AdjustObjectBoxRotationOnSurface(objectBox, objectSurfaceData, brushElement); AdjustObjectBoxCenterToSitOnSurface(objectBox, objectSurfaceData, brushElement); if (!_workingBrushCircle.ContainsPoint(_workingBrushCircle.Plane.ProjectPoint(objectBox.Center))) { continue; } // Recalculate the object matrix using the new box data TransformMatrix objectMatrix = matrixObjectBoxPair.ObjectMatrix; objectMatrix.Rotation = objectBox.Rotation; objectMatrix.Translation = ObjectPositionCalculator.CalculateObjectHierarchyPosition(brushElement.Prefab, objectBox.Center, objectMatrix.Scale, objectBox.Rotation); // We have been modifying the matrix and box data independently so we will ensure that the box uses the latest data OrientedBox finalBox = new OrientedBox(objectBox); finalBox.SetTransformMatrix(objectMatrix); // If the slope test passed, we will calculate an object placement data instance. Otherwise, we will just insert a new node. if (passesSlopeTest && DoesBoxPassObjectIntersectionTest(finalBox, brushElement.Prefab.UnityPrefab, objectMatrix)) { objectPlacementDataInstances.Add(new ObjectPlacementData(objectMatrix, brushElement.Prefab, brushElement.MustEmbedInSurface)); } _objectNodeNetwork.InsertAfterNode(objectBox, objectSurfaceData, 0); } } } // Adjust the prefab rotations for the next time the function is called if (_elementToNewPrefabRotation.Count != 0) { foreach (var prefabRotationPair in _elementToNewPrefabRotation) { DecorPaintObjectPlacementBrushElement brushElement = prefabRotationPair.Key; if (_elementToCurrentPrefabRotation.ContainsKey(brushElement)) { _elementToCurrentPrefabRotation[brushElement] = prefabRotationPair.Value; } } } return(objectPlacementDataInstances); }
private void RenderPrefabPreviewRows() { for (int prefabIndex = 0; prefabIndex < _filteredPrefabs.Count; ++prefabIndex) { // Start a new row? if (prefabIndex % ViewData.NumberOfPrefabsPerRow == 0) { if (prefabIndex != 0) { EditorGUILayout.EndHorizontal(); } EditorGUILayout.BeginHorizontal(); } // Render the prefab entry Prefab prefab = _filteredPrefabs[prefabIndex]; var previewButtonRenderData = new PrefabPreviewButtonRenderData(); previewButtonRenderData.ExtractFromPrefab(prefab, ViewData.PrefabPreviewScale); EditorGUILayout.BeginVertical(GUILayout.Width(previewButtonRenderData.ButtonWidth)); // Render the prefab preview button EditorGUIColor.Push(prefab == _prefabCategory.ActivePrefab ? ViewData.ActivePrefabTint : Color.white); if (EditorGUILayoutEx.PrefabPreview(prefab, true, previewButtonRenderData)) { ObjectPlacementSettings placementSettings = ObjectPlacementSettings.Get(); if (placementSettings.ObjectPlacementMode == ObjectPlacementMode.DecorPaint && placementSettings.DecorPaintObjectPlacementSettings.DecorPaintMode == DecorPaintMode.Brush && DecorPaintObjectPlacementBrushDatabase.Get().ActiveBrush != null && Event.current.button == (int)MouseButton.Right) { UndoEx.RecordForToolAction(DecorPaintObjectPlacementBrushDatabase.Get().ActiveBrush); DecorPaintObjectPlacementBrushElement brushElement = DecorPaintObjectPlacementBrushDatabase.Get().ActiveBrush.CreateNewElement(); brushElement.Prefab = prefab; Octave3DWorldBuilder.ActiveInstance.RepaintAllEditorWindows(); Octave3DWorldBuilder.ActiveInstance.Inspector.Repaint(); } else if (Octave3DWorldBuilder.ActiveInstance.Inspector.ActiveInspectorGUIIdentifier == InspectorGUIIdentifier.ObjectSelection && AllShortcutCombos.Instance.ReplacePrefabsForSelectedObjects.IsActive()) { ObjectSelection.Get().ReplaceSelectedObjectsWithPrefab(prefab); } else { UndoEx.RecordForToolAction(_prefabCategory); _prefabCategory.SetActivePrefab(prefab); } } EditorGUIColor.Pop(); // Render the prefab name labels if necessary if (ViewData.ShowPrefabNames) { Rect previewRectangle = GUILayoutUtility.GetLastRect(); EditorGUILayoutEx.LabelInMiddleOfControlRect(previewRectangle, prefab.Name, previewButtonRenderData.ButtonHeight, GetStyleForPrefabNameLabel()); } // Render the remove prefab button if (GUILayout.Button(GetRemovePrefabButtonContent())) { UndoEx.RecordForToolAction(_prefabCategory); _prefabCategory.RemoveAndDestroyPrefab(prefab); Octave3DWorldBuilder.ActiveInstance.Inspector.Repaint(); } EditorGUILayout.EndVertical(); } // End the last row (if any) if (_filteredPrefabs.Count != 0) { EditorGUILayout.EndHorizontal(); } }
public DecorPaintObjectPlacementBrushElementView(DecorPaintObjectPlacementBrushElement brushElement) { _brushElement = brushElement; SurroundWithBox = true; }
public bool ContainsElement(DecorPaintObjectPlacementBrushElement element) { return(_elements.Contains(element)); }
protected override void RenderContent() { RenderNameChangeField(); RenderRadiusField(); RenderMaxNumberOfObjectsField(); RenderDistanceBetweenObjectsField(); RenderIgnoreObjectsOutsideOfPaintSurfaceToggle(); RenderDestinationCategoryForElementPrefabsSelectionPopup(); EditorGUILayout.Separator(); if (!_brush.IsEmpty) { Octave3DWorldBuilder.ActiveInstance.ShowGUIHint("Left click on an element's preview to change its parameters. Right click to remove the element from the brush. SHIFT + Right click to toggle the element on/off."); } Data.ElementsScrollPos = EditorGUILayout.BeginScrollView(Data.ElementsScrollPos, "Box", GUILayout.Height(Data.ElementsScrollViewHeight)); if (_brush.IsEmpty) { EditorGUILayout.HelpBox("There are no brush elements available. You can drag and drop prefabs onto this area or " + "right click on prefabs inside the active category to create new elements. All brush elements will " + "be shown with a preview inside this area.", UnityEditor.MessageType.None); } else { List <DecorPaintObjectPlacementBrushElement> allBrushElements = _brush.GetAllBrushElements(); for (int brushElemIndex = 0; brushElemIndex < allBrushElements.Count; ++brushElemIndex) { if (brushElemIndex % Data.NumElementsPerRow == 0) { if (brushElemIndex != 0) { EditorGUILayout.EndHorizontal(); } EditorGUILayout.BeginHorizontal(); } DecorPaintObjectPlacementBrushElement brushElement = allBrushElements[brushElemIndex]; var previewButtonRenderData = new PrefabPreviewButtonRenderData(); previewButtonRenderData.ExtractFromPrefab(brushElement.Prefab, Data.ElementPreviewScale); Color previewTint = brushElement != _brush.ActiveElement ? Color.white : Data.ActiveElementTintColor; if (_brush.ActiveElement != brushElement && !brushElement.IsEnabled) { previewTint = Data.DisabledElementTintColor; } EditorGUILayout.BeginVertical(GUILayout.Width(previewButtonRenderData.ButtonWidth)); EditorGUIColor.Push(previewTint); if (EditorGUILayoutEx.PrefabPreview(brushElement.Prefab, true, previewButtonRenderData)) { if (Event.current.button == (int)MouseButton.Left) { if (brushElement != _brush.ActiveElement) { UndoEx.RecordForToolAction(_brush); _brush.SetActiveElement(brushElement); } } else if (Event.current.button == (int)MouseButton.Right) { if (!Event.current.shift) { UndoEx.RecordForToolAction(_brush); _brush.RemoveAndDestroyElement(brushElement); } else { UndoEx.RecordForToolAction(brushElement); brushElement.IsEnabled = !brushElement.IsEnabled; } } } EditorGUIColor.Pop(); EditorGUILayout.EndVertical(); } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); Rect prefabDropRect = GUILayoutUtility.GetLastRect(); EditorGUILayout.BeginHorizontal(); var content = new GUIContent(); content.text = "Load active category"; content.tooltip = "Loads all the prefabs from the active category inside the active brush. Note: Prefabs which already exist in the brush, will be ignored."; if (GUILayout.Button(content, GUILayout.Width(130.0f))) { UndoEx.RecordForToolAction(_brush); _brush.LoadAllPrefabsInActiveCategory(); } RenderRemoveAllElementsButton(); content.text = "Look and feel..."; content.tooltip = "Opens up a new window which allows you to control the look and feel of the brush elements view."; if (GUILayout.Button(content, GUILayout.Width(110.0f))) { Octave3DWorldBuilder.ActiveInstance.EditorWindowPool.DecorPaintBrushViewLookAndFeelWindow.ViewData = Data; Octave3DWorldBuilder.ActiveInstance.EditorWindowPool.DecorPaintBrushViewLookAndFeelWindow.ShowOctave3DWindow(); } EditorGUILayout.EndHorizontal(); if (_brush.ActiveElement != null) { EditorGUILayout.Separator(); _brush.ActiveElement.View.Render(); } PrefabsToDecorPaintBrushEventHandler.Get().DropDest = PrefabsToDecorPaintBrushEventHandler.DropDestination.Brush; PrefabsToDecorPaintBrushEventHandler.Get().DestinationBrush = _brush; PrefabsToDecorPaintBrushEventHandler.Get().Handle(Event.current, prefabDropRect); }