예제 #1
0
        ///<summary>
        ///Align a group of bricks on any intersecting geometry with a collider. Provide a fallback plane if nothing is hit
        ///</summary>
        ///<param name="sourceBrick">The brick we center this aligning around <paramref name="pickupOffset"/></param>
        ///<param name="bricks">The list of bricks</param>
        ///<param name="bounds">The current world space bounds of the bricks</param>
        ///<param name="pivot">The pivot used for rotating the bricks into an aligned orientation</param>
        ///<param name="pickupOffset">The offset at which we picked up the source brick</param>
        ///<param name="ray">The mouse ray we shoot into the scene</param>
        ///<param name="fallbackWorldPlane">A fallback plane if no geometry is hit</param>
        ///<param name="offset">Out parameter for the offset needed to place bricks on intersecting geometry</param>
        ///<param name="alignedOffset">Out parameter for the offset aligned to LU_10</param>
        ///<param name="rotation">Out parameter for the rotation needed to align</param>
        ///<param name="hit">Output parameter for hit information</param>
        public static void AlignBricks(Brick sourceBrick, HashSet <Brick> bricks, Bounds bounds, Vector3 pivot,
                                       Vector3 pickupOffset, Ray ray, Plane fallbackWorldPlane, float maxDistance, out Vector3 offset, out Vector3 alignedOffset, out Quaternion rotation, out RaycastHit hit)
        {
            // Steps for placing selected bricks on intersecting geometry:
            // 1. Find a hit point (Raycast) on either geometry or fallback plane
            // 3. Find the closest axis of the focusbrick to the normal of the plane
            // 4. Orient all bricks around a pivot so that the found axis of the focusbrick aligns with the plane normal
            // 5. Compute bounds in the space of the plane (So they are properly aligned)
            // 6. Find out how much we need to offset to get above the grid/hit plane

            if (GetGridAlignedPosition(ray, fallbackWorldPlane, sourceBrick.gameObject.scene.GetPhysicsScene(), maxDistance, out hit))
            {
                // Any hit will have a normal (either geometry hit or fallback plane)
                var normal = hit.normal;

                // Check all possible world axes of source brick transform and find the one that is
                // closest (by angle) to the plane normal

                // 1. Get rotation to align up with normal
                // 2. Cache rotation and apply aligned rotation to sourceBrick temporarily
                // 3. Get rotation needed to align with forward/right vectors
                // 4. Revert sourceBrick to cached rotation

                // Compute the rotation required to get the closest axis aligned to the plane normal
                var        closestAxis     = MathUtils.FindClosestAxis(sourceBrick.transform, normal, out MathUtils.VectorDirection vectorDirection);
                Quaternion rot             = Quaternion.FromToRotation(closestAxis, normal);
                var        cachedSourceRot = sourceBrick.transform.rotation;
                sourceBrick.transform.rotation = rot * sourceBrick.transform.rotation;

                var axesToAlign = MathUtils.GetRelatedAxes(sourceBrick.transform, vectorDirection);

                // Compute the transformation matrix for the plane
                var origin = Vector3.zero;

                var up      = Vector3.zero;
                var right   = Vector3.zero;
                var forward = Vector3.zero;

                if (hit.collider != null)
                {
                    var hitTransform = hit.collider.transform;

                    // Find the axis closest to the normal on the transform
                    // to make sure we don't choose to align all bricks to the normal again
                    var hitRightAngle   = Vector3.Angle(normal, hitTransform.right);
                    var hitUpAngle      = Vector3.Angle(normal, hitTransform.up);
                    var hitForwardAngle = Vector3.Angle(normal, hitTransform.forward);
                    var hitLeftAngle    = Vector3.Angle(normal, -hitTransform.right);
                    var hitDownAngle    = Vector3.Angle(normal, -hitTransform.up);
                    var hitBackAngle    = Vector3.Angle(normal, -hitTransform.forward);

                    var transformAxes = new List <Vector3>();

                    // Align the rotation of the transform to the normal
                    if (hitRightAngle <= hitUpAngle && hitRightAngle <= hitForwardAngle && hitRightAngle <= hitLeftAngle &&
                        hitRightAngle <= hitDownAngle && hitRightAngle <= hitBackAngle)
                    {
                        // normal points right
                        var fromTo = Quaternion.FromToRotation(hitTransform.right, normal);
                        transformAxes.Add(fromTo * hitTransform.up);
                        transformAxes.Add(fromTo * hitTransform.forward);
                    }
                    else if (hitUpAngle <= hitRightAngle && hitUpAngle <= hitForwardAngle && hitUpAngle <= hitLeftAngle &&
                             hitUpAngle <= hitDownAngle && hitUpAngle <= hitBackAngle)
                    {
                        // normal points up
                        transformAxes.Add(hitTransform.right);
                        transformAxes.Add(hitTransform.forward);
                    }
                    else if (hitForwardAngle <= hitRightAngle && hitForwardAngle <= hitUpAngle && hitForwardAngle <= hitLeftAngle &&
                             hitForwardAngle <= hitDownAngle && hitForwardAngle <= hitBackAngle)
                    {
                        // normal points forward
                        var fromTo = Quaternion.FromToRotation(hitTransform.forward, normal);
                        transformAxes.Add(fromTo * hitTransform.up);
                        transformAxes.Add(fromTo * hitTransform.right);
                    }
                    else if (hitLeftAngle <= hitUpAngle && hitLeftAngle <= hitForwardAngle && hitLeftAngle <= hitRightAngle &&
                             hitLeftAngle <= hitDownAngle && hitLeftAngle <= hitBackAngle)
                    {
                        // normal points left
                        var fromTo = Quaternion.FromToRotation(-hitTransform.right, normal);
                        transformAxes.Add(fromTo * -hitTransform.up);
                        transformAxes.Add(fromTo * -hitTransform.forward);
                    }
                    else if (hitDownAngle <= hitRightAngle && hitDownAngle <= hitForwardAngle && hitDownAngle <= hitLeftAngle &&
                             hitDownAngle <= hitUpAngle && hitDownAngle <= hitBackAngle)
                    {
                        // normal points down
                        var fromTo = Quaternion.FromToRotation(-hitTransform.up, normal);
                        transformAxes.Add(fromTo * -hitTransform.right);
                        transformAxes.Add(fromTo * -hitTransform.forward);
                    }
                    else if (hitBackAngle <= hitRightAngle && hitBackAngle <= hitUpAngle && hitBackAngle <= hitLeftAngle &&
                             hitBackAngle <= hitDownAngle && hitBackAngle <= hitForwardAngle)
                    {
                        // normal points back
                        var fromTo = Quaternion.FromToRotation(-hitTransform.forward, normal);
                        transformAxes.Add(fromTo * -hitTransform.up);
                        transformAxes.Add(fromTo * -hitTransform.right);
                    }

                    up      = normal * hitTransform.localScale.y;
                    right   = transformAxes[0] * hitTransform.localScale.x;
                    forward = transformAxes[1] * hitTransform.localScale.z;

                    var m = Matrix4x4.TRS(hitTransform.position, Quaternion.identity, Vector3.one);
                    m.SetColumn(0, forward);
                    m.SetColumn(1, up);
                    m.SetColumn(2, right);

                    rot = MathUtils.AlignRotation(new Vector3[] { axesToAlign.Item1, axesToAlign.Item2 }, m) * rot;

                    // We want to find a common origin for all bricks hitting this specific transform
                    // The origin needs to align with some common point. It doesn't matter what that is, as long
                    // as it is common for this specific transform.
                    // The alignment should only happen in the XZ plane of the transform, which means alignment
                    // on the local Y-axis is always 0 in plane space.

                    var localOrigin = m.inverse.MultiplyPoint(hitTransform.position);
                    var localHit    = m.inverse.MultiplyPoint(hit.point);

                    localOrigin.y = localHit.y;
                    origin        = m.MultiplyPoint(localOrigin);
                }
                else
                {
                    rot     = MathUtils.AlignRotation(new Vector3[] { axesToAlign.Item1, axesToAlign.Item2 }, Matrix4x4.identity) * rot;
                    forward = Vector3.forward;
                    up      = Vector3.up;
                    right   = Vector3.right;
                }

                sourceBrick.transform.rotation = cachedSourceRot;

                rotation = rot;
                rot.ToAngleAxis(out float angle, out Vector3 axis);

                var oldPositions = new List <Vector3>();
                var oldRotations = new List <Quaternion>();

                foreach (var brick in bricks)
                {
                    oldPositions.Add(brick.transform.position);
                    oldRotations.Add(brick.transform.rotation);

                    brick.transform.RotateAround(pivot, axis, angle);
                }

                var planeTRS = Matrix4x4.TRS(origin, Quaternion.identity, Vector3.one);
                planeTRS.SetColumn(0, forward);
                planeTRS.SetColumn(1, up);
                planeTRS.SetColumn(2, right);

                // Now compute how much we need to offset the aligned brick selection to get it above the intersected area
                offset = GetOffsetToGrid(sourceBrick.transform.position, bricks, ray, pickupOffset, planeTRS.inverse);

                // Find out how far we are dragging the bricks along the ray to align with the plane.
                var localBoundsMin = planeTRS.inverse.MultiplyPoint(bounds.min);
                var boundsPos      = localBoundsMin + offset;

                // If it is too far, drag it along the normal instead.
                if (Vector3.Distance(localBoundsMin, boundsPos) > 10.0f)
                {
                    var newRay = new Ray(hit.point + normal * 20.0f, -normal);
                    offset = GetOffsetToGrid(sourceBrick.transform.position, bricks, newRay, pickupOffset, planeTRS.inverse);
                }

                // Transform to world space
                offset = planeTRS.MultiplyVector(offset);

                var newPos = offset + sourceBrick.transform.position;
                offset = newPos - sourceBrick.transform.position;

                var transformation = Matrix4x4.TRS(origin, Quaternion.identity, Vector3.one);
                transformation.SetColumn(0, forward.normalized);
                transformation.SetColumn(1, up.normalized);
                transformation.SetColumn(2, right.normalized);

                AlignToGrid(ref newPos, transformation, LU_10);
                alignedOffset = newPos - sourceBrick.transform.position;

                var j = 0;
                foreach (var brick in bricks)
                {
                    brick.transform.position = oldPositions[j];
                    brick.transform.rotation = oldRotations[j++];
                }
            }
            else
            {
                // In case there was no hit, get the offset required to place the bricks at a fixed distance along the ray
                var pointOnRay = ray.GetPoint(maxDistance);
                offset = pointOnRay - sourceBrick.transform.position;
                AlignToGrid(ref pointOnRay, Matrix4x4.identity, LU_10);
                alignedOffset = pointOnRay - sourceBrick.transform.position;
                rotation      = Quaternion.identity;
            }
        }
        /// <summary>
        /// Recompute the pivot of a transform containing bricks.
        /// </summary>
        /// <param name="parent">The transform to recompute the pivot for</param>
        /// <param name="pivotType">The pivot type</param>
        /// <param name="alignRotation">Whether we also want to align rotation relative to bricks</param>
        /// <param name="undoBehavior">Whether or not to register undo for this action</param>
        public static void RecomputePivot(Transform parent, Model.Pivot pivotType = Model.Pivot.BottomCenter, bool alignRotation = true, UndoBehavior undoBehavior = UndoBehavior.withUndo)
        {
            if (pivotType == Model.Pivot.Original)
            {
                return;
            }

            var   bricks           = parent.GetComponentsInChildren <Brick>();
            Brick referenceBrick   = null;
            var   closestAngleToUp = 100000.0f;

            foreach (var brick in bricks)
            {
                var newAngle = Vector3.Angle(brick.transform.up, parent.up);
                if (newAngle < closestAngleToUp)
                {
                    closestAngleToUp = newAngle;
                    referenceBrick   = brick;
                }
            }
            if (!referenceBrick)
            {
                return;
            }

            var       oldRotations   = new List <Quaternion>();
            var       oldPositions   = new List <Vector3>();
            Matrix4x4 transformation = referenceBrick.transform.localToWorldMatrix.inverse;
            var       bounds         = BrickBuildingUtility.ComputeBounds(bricks, transformation);
            var       pivot          = bounds.center;

            if (pivotType == Model.Pivot.BottomCenter)
            {
                pivot += Vector3.down * bounds.extents.y;
            }

            pivot = referenceBrick.transform.TransformPoint(pivot);

            if (Vector3.Distance(parent.position, pivot) < 0.00001f)
            {
                return;
            }

            if (undoBehavior == UndoBehavior.withUndo)
            {
                var collectedTransforms = new List <Transform>();
                collectedTransforms.Add(parent);
                foreach (Transform child in parent)
                {
                    collectedTransforms.Add(child);
                }
                Undo.RegisterCompleteObjectUndo(collectedTransforms.ToArray(), "Recording groups before moving model group");
            }

            var difference = parent.position - pivot;

            parent.position = pivot;

            foreach (Transform child in parent)
            {
                child.transform.position += difference;

                if (alignRotation)
                {
                    oldRotations.Add(child.transform.rotation);
                    oldPositions.Add(child.transform.position);
                }
            }

            if (alignRotation)
            {
                var closest = MathUtils.FindClosestAxis(parent, referenceBrick.transform.up, out MathUtils.VectorDirection direction);
                var rot     = Quaternion.FromToRotation(closest, referenceBrick.transform.up);

                var forward = referenceBrick.transform.forward;
                var right   = referenceBrick.transform.right;

                var oldRot = parent.rotation;
                parent.rotation = rot * parent.rotation;

                var m = Matrix4x4.TRS(parent.position, Quaternion.identity, Vector3.one);
                m.SetColumn(0, forward);
                switch (direction)
                {
                case MathUtils.VectorDirection.up:
                    m.SetColumn(1, parent.up);
                    break;

                case MathUtils.VectorDirection.right:
                    m.SetColumn(1, parent.right);
                    break;

                case MathUtils.VectorDirection.forward:
                    m.SetColumn(1, parent.forward);
                    break;

                case MathUtils.VectorDirection.down:
                    m.SetColumn(1, -parent.up);
                    break;

                case MathUtils.VectorDirection.left:
                    m.SetColumn(1, -parent.right);
                    break;

                case MathUtils.VectorDirection.back:
                    m.SetColumn(1, -parent.forward);
                    break;
                }
                m.SetColumn(2, right);

                var related = MathUtils.GetRelatedAxes(parent, direction);
                rot = MathUtils.AlignRotation(new Vector3[] { related.Item1, related.Item2 }, m) * rot;
                rot.ToAngleAxis(out float angle, out Vector3 axis);
                parent.rotation = oldRot;
                parent.RotateAround(parent.position, axis, angle);

                var i = 0;
                foreach (Transform child in parent)
                {
                    child.transform.rotation = oldRotations[i];
                    child.transform.position = oldPositions[i++];
                }
            }
        }
        public static void RecomputePivot(Transform parent, Model.Pivot pivotType = Model.Pivot.BottomCenter, bool alignRotation = true, UndoBehavior undoBehavior = UndoBehavior.withUndo)
        {
            if (pivotType == Model.Pivot.Original)
            {
                return;
            }

            var bricks         = parent.GetComponentsInChildren <Brick>();
            var referenceBrick = bricks.FirstOrDefault();

            if (!referenceBrick)
            {
                return;
            }

            var       oldRotations   = new List <Quaternion>();
            var       oldPositions   = new List <Vector3>();
            Matrix4x4 transformation = referenceBrick.transform.localToWorldMatrix.inverse;
            var       bounds         = BrickBuildingUtility.ComputeBounds(bricks, transformation);
            var       pivot          = bounds.center;

            if (pivotType == Model.Pivot.BottomCenter)
            {
                pivot += Vector3.down * bounds.extents.y;
            }

            pivot = referenceBrick.transform.TransformPoint(pivot);

            if (Vector3.Distance(parent.position, pivot) < float.Epsilon)
            {
                return;
            }

            if (undoBehavior == UndoBehavior.withUndo)
            {
                var collectedTransforms = new List <Transform>();
                collectedTransforms.Add(parent);
                for (var i = 0; i < parent.childCount; i++)
                {
                    var child = parent.GetChild(i);
                    collectedTransforms.Add(child);
                }
                Undo.RegisterCompleteObjectUndo(collectedTransforms.ToArray(), "Recording groups before moving model group");
            }

            var difference = parent.position - pivot;

            parent.position = pivot;

            for (var i = 0; i < parent.childCount; i++)
            {
                var child = parent.GetChild(i);
                child.transform.position += difference;

                if (alignRotation)
                {
                    oldRotations.Add(child.transform.rotation);
                    oldPositions.Add(child.transform.position);
                }
            }

            if (alignRotation)
            {
                var rot = Quaternion.FromToRotation(parent.up, referenceBrick.transform.up);

                var forward = referenceBrick.transform.forward;
                var right   = referenceBrick.transform.right;

                var oldRot = parent.rotation;
                parent.rotation = rot * parent.rotation;

                var m = Matrix4x4.TRS(parent.position, Quaternion.identity, Vector3.one);
                m.SetColumn(0, forward);
                m.SetColumn(1, parent.up);
                m.SetColumn(2, right);

                rot = MathUtils.AlignRotation(new Vector3[] { parent.right, parent.forward }, m) * rot;
                rot.ToAngleAxis(out float angle, out Vector3 axis);
                parent.rotation = oldRot;
                parent.RotateAround(parent.position, axis, angle);

                for (var i = 0; i < parent.childCount; i++)
                {
                    var child = parent.GetChild(i);
                    child.transform.rotation = oldRotations[i];
                    child.transform.position = oldPositions[i];
                }
            }
        }