示例#1
0
 private void AdjustObjectBoxRotationOnSurface(OrientedBox objectBox, ObjectSurfaceData objectSurfaceData, DecorPaintObjectPlacementBrushElement brushElement)
 {
     if (brushElement.AlignToSurface)
     {
         objectBox.Rotation = AxisAlignment.CalculateRotationQuaternionForAxisAlignment(objectBox.Rotation, brushElement.AlignmentAxis, objectSurfaceData.SurfaceNormal);
     }
 }
示例#2
0
 private bool DoesObjectSurfacePassSlopeTest(ObjectSurfaceData objectSurfaceData, DecorPaintObjectPlacementBrushElement brushElement)
 {
     if (!_workingBrushCircle.IsSittingOnTerrain && brushElement.SlopeSettings.UseSlopeOnlyForTerrainObjects)
     {
         return(true);
     }
     return(brushElement.SlopeSettings.IsNormalInSlopeRange(objectSurfaceData.SurfaceNormal));
 }
示例#3
0
            public void InsertAfterNode(OrientedBox orientedBox, ObjectSurfaceData objectSurfaceData, int indexOfPreviousNode)
            {
                if (NumberOfNodes == 0)
                {
                    return;
                }

                indexOfPreviousNode %= NumberOfNodes;
                _nodes.Insert(indexOfPreviousNode + 1, new ObjectNode(orientedBox, objectSurfaceData));
            }
示例#4
0
 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;
     }
 }
示例#5
0
        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));
        }
示例#6
0
 public ObjectSurfaceData(ObjectSurfaceData source)
 {
     _basePosition  = source.BasePosition;
     _surfaceNormal = source.SurfaceNormal;
 }
示例#7
0
        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);
        }
示例#8
0
 public void AddNodeToEnd(OrientedBox orientedBox, ObjectSurfaceData objectSurfaceData)
 {
     _nodes.Add(new ObjectNode(orientedBox, objectSurfaceData));
 }
示例#9
0
 public ObjectNode(OrientedBox objectBox, ObjectSurfaceData objectSurfaceData)
 {
     _objectBox         = new OrientedBox(objectBox);
     _objectSurfaceData = new ObjectSurfaceData(objectSurfaceData);
 }