/// <summary>
        /// Draws two concentric arcs, depending on the <paramref name="thickness"/> parameter.
        /// </summary>
        /// <param name="center">Position of the arc center</param>
        /// <param name="radius">Radius of the outer arc.</param>
        /// <param name="arc">Length of the arc in range [0, 360] degrees.</param>
        /// <param name="thickness">Determines radius of the inner arc, in range [0, 1]. 0 specifies the inner arc is
        /// the same radius as the outer arc, while 1 specifies the inner arc has zero radius.</param>
        /// <param name="color">Color to draw the arc with.</param>
        private static void DrawArcWithThickness(Vector3 center, float radius, Radian arc, float thickness, Color color)
        {
            Gizmos.Color = color;
            if (arc.Degrees > 0.0f)
            {
                Gizmos.DrawWireArc(center, -Vector3.ZAxis, radius, new Degree(0.0f), arc);

                if (thickness > 0.0f)
                {
                    float innerRadius = radius * (1.0f - MathEx.Clamp01(thickness));
                    if (thickness < 1.0f)
                    {
                        Gizmos.Color = color * new Color(0.5f, 0.5f, 0.5f);
                        Gizmos.DrawWireArc(center, -Vector3.ZAxis, innerRadius, new Degree(0.0f), arc);
                    }

                    Gizmos.Color = color * new Color(0.25f, 0.25f, 0.25f);

                    for (Radian x = new Radian(0.0f); x < arc; x += MathEx.HalfPi)
                    {
                        Vector3 dir = new Vector3(MathEx.Cos(x), MathEx.Sin(x), 0.0f);
                        Gizmos.DrawLine(center + dir * innerRadius, center + dir * radius);
                    }

                    if (!MathEx.ApproxEquals(arc.Degrees % 90.0f, 0.0f))
                    {
                        Vector3 dir = new Vector3(MathEx.Cos(arc), MathEx.Sin(arc), 0.0f);
                        Gizmos.DrawLine(center + dir * innerRadius, center + dir * radius);
                    }
                }
            }
        }
Beispiel #2
0
        /// <summary>
        /// Initializes the quaternion so that it orients an object so it faces in te provided direction.
        /// </summary>
        /// <param name="forward">Direction to orient the object towards.</param>
        /// <param name="up">Axis that determines the upward direction of the object.</param>
        public void SetLookRotation(Vector3 forward, Vector3 up)
        {
            Vector3 forwardNrm = Vector3.Normalize(forward);
            Vector3 upNrm      = Vector3.Normalize(up);

            if (MathEx.ApproxEquals(Vector3.Dot(forwardNrm, upNrm), 1.0f))
            {
                SetLookRotation(forwardNrm);
                return;
            }

            Vector3 x = Vector3.Cross(forwardNrm, upNrm);
            Vector3 y = Vector3.Cross(x, forwardNrm);

            x.Normalize();
            y.Normalize();

            this = Quaternion.FromAxes(x, y, -forwardNrm);
        }
        /// <summary>
        /// Draws a border between two arcs.
        /// </summary>
        /// <param name="topRadius">Radius of the top arc.</param>
        /// <param name="baseRadius">Radius of the base arc.</param>
        /// <param name="arc">Length of the arc in range [0, 360] degrees.</param>
        /// <param name="length">Distance between the two arcs.</param>
        private static void DrawConeBorder(float topRadius, float baseRadius, Radian arc, float length)
        {
            for (Radian x = new Radian(0.0f); x < arc; x += MathEx.HalfPi)
            {
                Vector3 dir = new Vector3(MathEx.Cos(x), MathEx.Sin(x), 0.0f);
                Vector3 a   = dir * baseRadius + new Vector3(0.0f, 0.0f, -length);
                Vector3 b   = dir * topRadius;

                Gizmos.DrawLine(a, b);
            }

            if (!MathEx.ApproxEquals(arc.Degrees % 90.0f, 0.0f))
            {
                Vector3 dir = new Vector3(MathEx.Cos(arc), MathEx.Sin(arc), 0.0f);
                Vector3 a   = dir * baseRadius + new Vector3(0.0f, 0.0f, -length);
                Vector3 b   = dir * topRadius;

                Gizmos.DrawLine(a, b);
            }
        }
Beispiel #4
0
        /// <summary>
        /// Adds a new keyframe to the animation curve, unless a keyframe with the same time already exists in which case
        /// the existing keyframe is updated with the new value. Newly added keyframe will use the automatic tangent mode.
        /// </summary>
        /// <param name="time">Time at which to add/update the keyframe.</param>
        /// <param name="value">Value of the keyframe.</param>
        internal void AddOrUpdateKeyframe(float time, float value)
        {
            int keyframeIdx = -1;

            for (int i = 0; i < keyFrames.Length; i++)
            {
                if (MathEx.ApproxEquals(keyFrames[i].time, time))
                {
                    keyframeIdx = i;
                    break;
                }
            }

            if (keyframeIdx != -1)
            {
                UpdateKeyframe(keyframeIdx, time, value);
            }
            else
            {
                AddKeyframe(time, value);
            }
        }
        /// <summary>
        /// Recalculates tangents for all keyframes using the keyframe values and set tangent modes.
        /// </summary>
        private void UpdateTangents()
        {
            if (keyFrames.Length == 0)
            {
                return;
            }

            if (keyFrames.Length == 1)
            {
                keyFrames[0].inTangent  = 0.0f;
                keyFrames[0].outTangent = 0.0f;

                return;
            }

            // First keyframe
            {
                KeyFrame keyThis = keyFrames[0];
                KeyFrame keyNext = keyFrames[1];

                keyThis.inTangent = 0.0f;

                TangentMode tangentMode = tangentModes[0];
                if (tangentMode == TangentMode.Auto || tangentMode.HasFlag(TangentMode.OutAuto) || tangentMode.HasFlag(TangentMode.OutLinear))
                {
                    float diff = keyNext.time - keyThis.time;

                    if (!MathEx.ApproxEquals(diff, 0.0f))
                    {
                        keyThis.outTangent = (keyNext.value - keyThis.value) / diff;
                    }
                    else
                    {
                        keyThis.outTangent = float.PositiveInfinity;
                    }
                }
                else if (tangentMode.HasFlag(TangentMode.OutStep))
                {
                    keyThis.outTangent = float.PositiveInfinity;
                }

                keyFrames[0] = keyThis;
            }

            // Inner keyframes
            for (int i = 1; i < keyFrames.Length - 1; i++)
            {
                KeyFrame keyPrev = keyFrames[i - 1];
                KeyFrame keyThis = keyFrames[i];
                KeyFrame keyNext = keyFrames[i + 1];

                keyThis.inTangent = 0.0f;

                TangentMode tangentMode = tangentModes[i];
                if (tangentMode == TangentMode.Auto) // Both automatic
                {
                    float diff = keyNext.time - keyPrev.time;

                    if (!MathEx.ApproxEquals(diff, 0.0f))
                    {
                        keyThis.outTangent = (keyNext.value - keyPrev.value) / diff;
                    }
                    else
                    {
                        keyThis.outTangent = float.PositiveInfinity;
                    }

                    keyThis.inTangent = keyThis.outTangent;
                }
                else if (tangentMode == TangentMode.Free) // Both free
                {
                    keyThis.inTangent = keyThis.outTangent;
                }
                else // Different per-tangent modes
                {
                    // In tangent
                    if (tangentMode.HasFlag(TangentMode.InAuto))
                    {
                        float diff = keyNext.time - keyPrev.time;

                        if (!MathEx.ApproxEquals(diff, 0.0f))
                        {
                            keyThis.inTangent = (keyNext.value - keyPrev.value) / diff;
                        }
                        else
                        {
                            keyThis.inTangent = float.PositiveInfinity;
                        }
                    }
                    else if (tangentMode.HasFlag(TangentMode.InLinear))
                    {
                        float diff = keyThis.time - keyPrev.time;

                        if (!MathEx.ApproxEquals(diff, 0.0f))
                        {
                            keyThis.inTangent = (keyThis.value - keyPrev.value) / diff;
                        }
                        else
                        {
                            keyThis.inTangent = float.PositiveInfinity;
                        }
                    }
                    else if (tangentMode.HasFlag(TangentMode.InStep))
                    {
                        keyThis.inTangent = float.PositiveInfinity;
                    }

                    // Out tangent
                    if (tangentMode.HasFlag(TangentMode.OutAuto))
                    {
                        float diff = keyNext.time - keyPrev.time;

                        if (!MathEx.ApproxEquals(diff, 0.0f))
                        {
                            keyThis.outTangent = (keyNext.value - keyPrev.value) / diff;
                        }
                        else
                        {
                            keyThis.outTangent = float.PositiveInfinity;
                        }
                    }
                    else if (tangentMode.HasFlag(TangentMode.OutLinear))
                    {
                        float diff = keyNext.time - keyThis.time;

                        if (!MathEx.ApproxEquals(diff, 0.0f))
                        {
                            keyThis.outTangent = (keyNext.value - keyThis.value) / diff;
                        }
                        else
                        {
                            keyThis.outTangent = float.PositiveInfinity;
                        }
                    }
                    else if (tangentMode.HasFlag(TangentMode.OutStep))
                    {
                        keyThis.outTangent = float.PositiveInfinity;
                    }
                }

                keyFrames[i] = keyThis;
            }

            // Last keyframe
            {
                KeyFrame keyThis = keyFrames[keyFrames.Length - 1];
                KeyFrame keyPrev = keyFrames[keyFrames.Length - 2];

                keyThis.outTangent = 0.0f;

                TangentMode tangentMode = tangentModes[tangentModes.Length - 1];
                if (tangentMode == TangentMode.Auto || tangentMode.HasFlag(TangentMode.InAuto) || tangentMode.HasFlag(TangentMode.InLinear))
                {
                    float diff = keyThis.time - keyPrev.time;

                    if (!MathEx.ApproxEquals(diff, 0.0f))
                    {
                        keyThis.inTangent = (keyThis.value - keyPrev.value) / diff;
                    }
                    else
                    {
                        keyThis.inTangent = float.PositiveInfinity;
                    }
                }
                else if (tangentMode.HasFlag(TangentMode.InStep))
                {
                    keyThis.inTangent = float.PositiveInfinity;
                }

                keyFrames[keyFrames.Length - 1] = keyThis;
            }
        }
Beispiel #6
0
        /// <summary>
        /// Draws the area between two curves using the provided color.
        /// </summary>
        /// <param name="curves">Curves to draw within the currently set range.</param>
        /// <param name="color">Color to draw the area with.</param>
        private void DrawCurveRange(EdAnimationCurve[] curves, Color color)
        {
            float range = GetRange(true);

            if (curves.Length != 2 || curves[0] == null || curves[1] == null)
            {
                return;
            }

            KeyFrame[][] keyframes = { curves[0].KeyFrames, curves[1].KeyFrames };
            if (keyframes[0].Length <= 0 || keyframes[1].Length <= 0)
            {
                return;
            }

            int   numSamples    = (drawableWidth + LINE_SPLIT_WIDTH - 1) / LINE_SPLIT_WIDTH;
            float timePerSample = range / numSamples;

            float time           = rangeOffset;
            float lengthPerPixel = rangeLength / drawableWidth;

            time -= lengthPerPixel * PADDING;

            int[] keyframeIndices = { 0, 0 };

            // Find first valid keyframe indices
            for (int curveIdx = 0; curveIdx < 2; curveIdx++)
            {
                keyframeIndices[curveIdx] = keyframes[curveIdx].Length;

                for (int i = 0; i < keyframes[curveIdx].Length; i++)
                {
                    if (keyframes[curveIdx][i].time > time)
                    {
                        keyframeIndices[curveIdx] = i;
                    }
                }
            }

            List <float> times = new List <float>();

            List <float>[] points = { new List <float>(), new List <float>() };

            // Determine start points
            for (int curveIdx = 0; curveIdx < 2; curveIdx++)
            {
                float value = curves[curveIdx].Evaluate(time, false);
                points[curveIdx].Add(value);
            }

            times.Add(time);

            float rangeEnd = rangeOffset + range;

            while (time < rangeEnd)
            {
                float nextTime = time + timePerSample;
                bool  hasStep  = false;

                // Determine time to sample at. Use fixed increments unless there's a step keyframe within our increment in
                // which case we use its time so we can evaluate it directly
                for (int curveIdx = 0; curveIdx < 2; curveIdx++)
                {
                    int keyframeIdx = keyframeIndices[curveIdx];
                    if (keyframeIdx < keyframes[curveIdx].Length)
                    {
                        KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];

                        bool isStep = keyframe.inTangent == float.PositiveInfinity ||
                                      keyframe.outTangent == float.PositiveInfinity;

                        if (isStep && keyframe.time <= nextTime)
                        {
                            nextTime = Math.Min(nextTime, keyframe.time);
                            hasStep  = true;
                        }
                    }
                }

                // Evaluate
                if (hasStep)
                {
                    for (int curveIdx = 0; curveIdx < 2; curveIdx++)
                    {
                        int keyframeIdx = keyframeIndices[curveIdx];
                        if (keyframeIdx < keyframes[curveIdx].Length)
                        {
                            KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];

                            if (MathEx.ApproxEquals(keyframe.time, nextTime))
                            {
                                if (keyframeIdx > 0)
                                {
                                    KeyFrame prevKeyframe = keyframes[curveIdx][keyframeIdx - 1];
                                    points[curveIdx].Add(prevKeyframe.value);
                                }
                                else
                                {
                                    points[curveIdx].Add(keyframe.value);
                                }

                                points[curveIdx].Add(keyframe.value);
                            }
                            else
                            {
                                // The other curve has step but this one doesn't, we just insert the same value twice
                                float value = curves[curveIdx].Evaluate(nextTime, false);
                                points[curveIdx].Add(value);
                                points[curveIdx].Add(value);
                            }

                            times.Add(nextTime);
                            times.Add(nextTime);
                        }
                    }
                }
                else
                {
                    for (int curveIdx = 0; curveIdx < 2; curveIdx++)
                    {
                        points[curveIdx].Add(curves[curveIdx].Evaluate(nextTime, false));
                    }

                    times.Add(nextTime);
                }

                // Advance keyframe indices
                for (int curveIdx = 0; curveIdx < 2; curveIdx++)
                {
                    int keyframeIdx = keyframeIndices[curveIdx];
                    while (keyframeIdx < keyframes[curveIdx].Length)
                    {
                        KeyFrame keyframe = keyframes[curveIdx][keyframeIdx];
                        if (keyframe.time > nextTime)
                        {
                            break;
                        }

                        keyframeIdx = ++keyframeIndices[curveIdx];
                    }
                }

                time = nextTime;
            }

            // End points
            for (int curveIdx = 0; curveIdx < 2; curveIdx++)
            {
                float value = curves[curveIdx].Evaluate(rangeEnd, false);
                points[curveIdx].Add(value);
            }

            times.Add(rangeEnd);

            int             numQuads = times.Count - 1;
            List <Vector2I> vertices = new List <Vector2I>();

            for (int i = 0; i < numQuads; i++)
            {
                int idxLeft  = points[0][i] < points[1][i] ? 0 : 1;
                int idxRight = points[0][i + 1] < points[1][i + 1] ? 0 : 1;

                Vector2[] left =
                {
                    new Vector2(times[i], points[0][i]),
                    new Vector2(times[i], points[1][i])
                };

                Vector2[] right =
                {
                    new Vector2(times[i + 1], points[0][i + 1]),
                    new Vector2(times[i + 1], points[1][i + 1])
                };

                if (idxLeft == idxRight)
                {
                    int idxA = idxLeft;
                    int idxB = (idxLeft + 1) % 2;

                    vertices.Add(CurveToPixelSpace(left[idxB]));
                    vertices.Add(CurveToPixelSpace(right[idxB]));
                    vertices.Add(CurveToPixelSpace(left[idxA]));

                    vertices.Add(CurveToPixelSpace(right[idxB]));
                    vertices.Add(CurveToPixelSpace(right[idxA]));
                    vertices.Add(CurveToPixelSpace(left[idxA]));
                }
                // Lines intersects, can't represent them with a single quad
                else if (idxLeft != idxRight)
                {
                    int idxA = idxLeft;
                    int idxB = (idxLeft + 1) % 2;

                    Line2 lineA = new Line2(left[idxB], right[idxA] - left[idxB]);
                    Line2 lineB = new Line2(left[idxA], right[idxB] - left[idxA]);

                    if (lineA.Intersects(lineB, out var t))
                    {
                        Vector2 intersection = left[idxB] + t * (right[idxA] - left[idxB]);

                        vertices.Add(CurveToPixelSpace(left[idxB]));
                        vertices.Add(CurveToPixelSpace(intersection));
                        vertices.Add(CurveToPixelSpace(left[idxA]));

                        vertices.Add(CurveToPixelSpace(intersection));
                        vertices.Add(CurveToPixelSpace(right[idxB]));
                        vertices.Add(CurveToPixelSpace(right[idxA]));
                    }
                }
            }

            canvas.DrawTriangleList(vertices.ToArray(), color, 129);
        }
Beispiel #7
0
        /// <summary>
        /// Tests saving, loading and updating of prefabs.
        /// </summary>
        private static void UnitTest4_Prefabs()
        {
            if (!EditorApplication.IsProjectLoaded)
            {
                Debug.LogWarning("Skipping unit test as no project is loaded.");
                return;
            }

            if (EditorApplication.IsSceneModified())
            {
                Debug.LogWarning("Cannot perform unit test as the current scene is modified.");
                return;
            }

            Action PrintSceneState = () =>
            {
                SceneObject root = Scene.Root;

                Stack <SceneObject> todo = new Stack <SceneObject>();
                todo.Push(root);

                StringBuilder output = new StringBuilder();
                while (todo.Count > 0)
                {
                    SceneObject so = todo.Pop();

                    int numChildren = so.GetNumChildren();
                    for (int i = numChildren - 1; i >= 0; i--)
                    {
                        SceneObject child = so.GetChild(i);

                        output.AppendLine(child.Name);
                        todo.Push(child);
                    }
                }

                Debug.Log(output);
            };

            // Disabled because it's a slow test, enable only when relevant (or when a build machine is set up)
            return;

            string oldScene = Scene.ActiveSceneUUID;

            Scene.Clear();

            try
            {
                // Simple scene save & load
                {
                    {
                        // unitTest4Scene_0.prefab:
                        // so0 (Comp1)
                        //  - so0_0
                        //  - so0_1 (Comp1)
                        //    - so0_1_0 (Comp1)
                        // so1 (Comp2)
                        //  - so1_0

                        SceneObject so0     = new SceneObject("so0");
                        SceneObject so1     = new SceneObject("so1");
                        SceneObject so0_0   = new SceneObject("so0_0");
                        SceneObject so0_1   = new SceneObject("so0_1");
                        SceneObject so1_0   = new SceneObject("so1_0");
                        SceneObject so0_1_0 = new SceneObject("so0_1_0");

                        so0_0.Parent   = so0;
                        so0_1.Parent   = so0;
                        so1_0.Parent   = so1;
                        so0_1_0.Parent = so0_1;

                        so0_1_0.LocalPosition = new Vector3(10.0f, 15.0f, 20.0f);
                        so0_1.LocalPosition   = new Vector3(1.0f, 2.0f, 3.0f);
                        so1_0.LocalPosition   = new Vector3(0, 123.0f, 0.0f);

                        UT1_Component1 comp0     = so0.AddComponent <UT1_Component1>();
                        UT1_Component2 comp1     = so1.AddComponent <UT1_Component2>();
                        UT1_Component1 comp1_1   = so0_1.AddComponent <UT1_Component1>();
                        UT1_Component1 comp0_1_0 = so0_1_0.AddComponent <UT1_Component1>();

                        comp0.otherSO        = so0_1_0;
                        comp0.otherComponent = comp1;

                        comp1_1.b = "originalValue2";

                        comp0_1_0.b               = "testValue";
                        comp0_1_0.otherSO         = so0;
                        comp0_1_0.otherComponent2 = comp0;

                        EditorApplication.SaveScene("unitTest4Scene_0.prefab");
                    }
                    {
                        EditorApplication.LoadScene("unitTest4Scene_0.prefab");

                        SceneObject sceneRoot = Scene.Root;
                        SceneObject so0       = sceneRoot.FindChild("so0", false);
                        SceneObject so1       = sceneRoot.FindChild("so1", false);
                        SceneObject so0_0     = so0.FindChild("so0_0", false);
                        SceneObject so0_1     = so0.FindChild("so0_1", false);
                        SceneObject so0_1_0   = so0_1.FindChild("so0_1_0", false);

                        Assert(so0_0 != null);
                        Assert(so0_1 != null);
                        Assert(so0_1_0 != null);

                        UT1_Component1 comp0     = so0.GetComponent <UT1_Component1>();
                        UT1_Component2 comp1     = so1.GetComponent <UT1_Component2>();
                        UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>();

                        Assert(comp0 != null);
                        Assert(comp1 != null);
                        Assert(comp0_1_0 != null);
                        Assert(comp0_1_0.b == "testValue");
                        Assert(comp0.otherSO == so0_1_0);
                        Assert(comp0.otherComponent == comp1);
                        Assert(comp0_1_0.otherSO == so0);
                        Assert(comp0_1_0.otherComponent2 == comp0);
                    }
                }

                Debug.Log("Passed stage 1");

                // Load & save a scene that contains a prefab and references its objects
                {
                    {
                        // unitTest4Scene_1.prefab:
                        // parentSO0
                        //  - [unitTest4Scene_0.prefab]
                        // parentSO1
                        //  - parentSO1_0 (Comp1)

                        Scene.Clear();

                        SceneObject parentSO0   = new SceneObject("parentSO0", false);
                        SceneObject parentSO1   = new SceneObject("parentSO1", false);
                        SceneObject parentSO1_0 = new SceneObject("parentSO1_0", false);

                        parentSO1_0.Parent      = parentSO1;
                        parentSO0.LocalPosition = new Vector3(50.0f, 50.0f, 50.0f);

                        UT1_Component1 parentComp1_0 = parentSO1_0.AddComponent <UT1_Component1>();

                        Prefab      scene0Prefab   = ProjectLibrary.Load <Prefab>("unitTest4Scene_0.prefab");
                        SceneObject prefabInstance = scene0Prefab.Instantiate();
                        prefabInstance.Parent        = parentSO0;
                        prefabInstance.LocalPosition = Vector3.Zero;

                        SceneObject so0     = prefabInstance.FindChild("so0", false);
                        SceneObject so1     = prefabInstance.FindChild("so1", false);
                        SceneObject so0_1   = so0.FindChild("so0_1", false);
                        SceneObject so1_0   = so1.FindChild("so1_0", false);
                        SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false);

                        UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>();

                        parentComp1_0.otherSO         = so1_0;
                        parentComp1_0.otherComponent2 = comp0_1_0;

                        EditorApplication.SaveScene("unitTest4Scene_1.prefab");
                    }
                    {
                        EditorApplication.LoadScene("unitTest4Scene_1.prefab");

                        SceneObject parentSO0   = Scene.Root.FindChild("parentSO0", false);
                        SceneObject parentSO1   = Scene.Root.FindChild("parentSO1", false);
                        SceneObject parentSO1_0 = parentSO1.FindChild("parentSO1_0", false);

                        UT1_Component1 parentComp1_0 = parentSO1_0.GetComponent <UT1_Component1>();

                        SceneObject prefabInstance = parentSO0.GetChild(0);
                        SceneObject so0            = prefabInstance.FindChild("so0", false);
                        SceneObject so1            = prefabInstance.FindChild("so1", false);
                        SceneObject so0_1          = so0.FindChild("so0_1", false);
                        SceneObject so1_0          = so1.FindChild("so1_0", false);
                        SceneObject so0_1_0        = so0_1.FindChild("so0_1_0", false);

                        UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>();

                        Assert(parentComp1_0.otherSO == so1_0);
                        Assert(parentComp1_0.otherComponent2 == comp0_1_0);
                    }
                }

                Debug.Log("Passed stage 2");

                // Modify prefab, reload the scene and ensure it is updated with modified prefab
                {
                    {
                        // unitTest4Scene_0.prefab:
                        // so0
                        //  - so0_1 (Comp1)
                        //    - so0_1_0 (Comp1)
                        // so1 (Comp1, Comp2)
                        //  - so1_0
                        //  - so1_1

                        Scene.Load("unitTest4Scene_0.prefab");

                        SceneObject sceneRoot = Scene.Root;
                        SceneObject so0       = sceneRoot.FindChild("so0", false);
                        SceneObject so0_0     = so0.FindChild("so0_0", false);
                        SceneObject so0_1     = so0.FindChild("so0_1", false);
                        SceneObject so1       = sceneRoot.FindChild("so1", false);
                        SceneObject so1_0     = so1.FindChild("so1_0", false);
                        SceneObject so0_1_0   = so0_1.FindChild("so0_1_0", false);

                        SceneObject so1_1 = new SceneObject("so1_1");
                        so1_1.Parent = so1;

                        so0.RemoveComponent <UT1_Component1>();
                        UT1_Component1 comp1     = so1.AddComponent <UT1_Component1>();
                        UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>();

                        so0_0.Destroy();

                        comp1.otherSO         = so1_0;
                        comp1.otherComponent2 = comp0_1_0;

                        comp0_1_0.otherSO         = so1_1;
                        comp0_1_0.otherComponent2 = comp1;
                        comp0_1_0.a = 123;
                        comp0_1_0.b = "modifiedValue";

                        so1.Name          = "so1_modified";
                        so1.LocalPosition = new Vector3(0, 999.0f, 0.0f);

                        EditorApplication.SaveScene("unitTest4Scene_0.prefab");
                    }

                    {
                        EditorApplication.LoadScene("unitTest4Scene_1.prefab");

                        SceneObject parentSO0   = Scene.Root.FindChild("parentSO0", false);
                        SceneObject parentSO1   = Scene.Root.FindChild("parentSO1", false);
                        SceneObject parentSO1_0 = parentSO1.FindChild("parentSO1_0", false);

                        UT1_Component1 parentComp1_0 = parentSO1_0.GetComponent <UT1_Component1>();

                        SceneObject prefabInstance = parentSO0.GetChild(0);
                        SceneObject so0            = prefabInstance.FindChild("so0", false);
                        SceneObject so1            = prefabInstance.FindChild("so1_modified", false);
                        SceneObject so0_0          = so0.FindChild("so0_0", false);
                        SceneObject so0_1          = so0.FindChild("so0_1", false);
                        SceneObject so1_0          = so1.FindChild("so1_0", false);
                        SceneObject so0_1_0        = so0_1.FindChild("so0_1_0", false);
                        SceneObject so1_1          = so1.FindChild("so1_1", false);

                        UT1_Component1 comp0     = so0.GetComponent <UT1_Component1>();
                        UT1_Component1 comp1     = so1.GetComponent <UT1_Component1>();
                        UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>();

                        Assert(parentComp1_0.otherSO == so1_0);
                        Assert(parentComp1_0.otherComponent2 == comp0_1_0);
                        Assert(so1_1 != null);
                        Assert(so0_0 == null);
                        Assert(comp0 == null);
                        Assert(comp0_1_0.otherSO == so1_1);
                        Assert(comp0_1_0.otherComponent2 == comp1);
                        Assert(comp0_1_0.a == 123);
                        Assert(comp0_1_0.b == "modifiedValue");
                        Assert(comp1.otherSO == so1_0);
                        Assert(comp1.otherComponent2 == comp0_1_0);
                        Assert(MathEx.ApproxEquals(so1.LocalPosition.y, 999.0f));
                    }
                }

                Debug.Log("Passed stage 3");

                // Make instance specific changes to the prefab, modify the prefab itself and ensure
                // both changes persist
                {
                    // Create new scene referencing the prefab and make instance modifications
                    {
                        // unitTest4Scene_2.prefab:
                        // parent2SO0
                        //  - [unitTest4Scene_0.prefab]
                        // parent2SO1
                        //  - parent2SO1_0 (Comp1)

                        // unitTest4Scene_0.prefab (unitTest4Scene_2.prefab instance):
                        // so0 (Comp1(INSTANCE))
                        //  - so0_0 (INSTANCE)
                        //  - so0_1 (Comp1)
                        //    - so0_1_0 (Comp1)
                        // so1 (Comp2)
                        //  - so1_0

                        Scene.Clear();

                        SceneObject parent2SO0   = new SceneObject("parent2SO0");
                        SceneObject parent2SO1   = new SceneObject("parent2SO1");
                        SceneObject parent2SO1_0 = new SceneObject("parent2SO1_0");

                        parent2SO1_0.Parent = parent2SO1;

                        UT1_Component1 parentComp1_0 = parent2SO1_0.AddComponent <UT1_Component1>();

                        Prefab      scene0Prefab   = ProjectLibrary.Load <Prefab>("unitTest4Scene_0.prefab");
                        SceneObject prefabInstance = scene0Prefab.Instantiate();
                        prefabInstance.Parent = parent2SO0;

                        SceneObject so0 = prefabInstance.FindChild("so0", false);
                        SceneObject so1 = prefabInstance.FindChild("so1_modified", false);

                        SceneObject so0_1   = so0.FindChild("so0_1", false);
                        SceneObject so1_0   = so1.FindChild("so1_0", false);
                        SceneObject so1_1   = so1.FindChild("so1_1", false);
                        SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false);

                        UT1_Component2 comp1     = so1.GetComponent <UT1_Component2>();
                        UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>();
                        UT1_Component1 comp0_1   = so0_1.GetComponent <UT1_Component1>();

                        SceneObject so0_0 = new SceneObject("so0_0");
                        so0_0.Parent = so0;
                        UT1_Component1 comp0 = so0.AddComponent <UT1_Component1>();

                        so1.RemoveComponent <UT1_Component1>();
                        so1_1.Destroy();

                        comp0.otherSO        = so0_1_0;
                        comp0.otherComponent = comp1;

                        parentComp1_0.otherSO         = so1_0;
                        parentComp1_0.otherComponent2 = comp0_1_0;

                        comp0_1_0.otherSO         = parent2SO1_0;
                        comp0_1_0.otherComponent2 = parentComp1_0;
                        comp0_1_0.b = "instanceValue";

                        comp0_1.b = "instanceValue2";

                        EditorApplication.SaveScene("unitTest4Scene_2.prefab");
                    }

                    Debug.Log("Passed stage 4.1");

                    // Reload the scene and ensure instance modifications remain
                    {
                        EditorApplication.LoadScene("unitTest4Scene_2.prefab");

                        SceneObject root         = Scene.Root;
                        SceneObject parent2SO0   = root.FindChild("parent2SO0", false);
                        SceneObject parent2SO1   = root.FindChild("parent2SO1", false);
                        SceneObject parent2SO1_0 = parent2SO1.FindChild("parent2SO1_0", false);

                        SceneObject prefabInstance = parent2SO0.GetChild(0);

                        SceneObject so0     = prefabInstance.FindChild("so0", false);
                        SceneObject so1     = prefabInstance.FindChild("so1_modified", false);
                        SceneObject so0_0   = so0.FindChild("so0_0", false);
                        SceneObject so0_1   = so0.FindChild("so0_1", false);
                        SceneObject so1_0   = so1.FindChild("so1_0", false);
                        SceneObject so1_1   = so1.FindChild("so1_1", false);
                        SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false);

                        UT1_Component1 parentComp1_0 = parent2SO1_0.GetComponent <UT1_Component1>();
                        UT1_Component1 comp0         = so0.GetComponent <UT1_Component1>();
                        UT1_Component2 comp1         = so1.GetComponent <UT1_Component2>();
                        UT1_Component1 comp11        = so1.GetComponent <UT1_Component1>();
                        UT1_Component1 comp0_1_0     = so0_1_0.GetComponent <UT1_Component1>();
                        UT1_Component1 comp0_1       = so0_1.GetComponent <UT1_Component1>();

                        Assert(so0_0 != null);
                        Assert(comp0 != null);
                        Assert(so1_1 == null);
                        Assert(comp11 == null);

                        Assert(comp0.otherSO == so0_1_0);
                        Assert(comp0.otherComponent == comp1);

                        Assert(parentComp1_0.otherSO == so1_0);
                        Assert(parentComp1_0.otherComponent2 == comp0_1_0);

                        Debug.Log(comp0_1_0.otherSO == null);
                        if (comp0_1_0.otherSO != null)
                        {
                            Debug.Log(comp0_1_0.otherSO.InstanceId + " - " + parent2SO1_0.InstanceId);
                        }

                        Assert(comp0_1_0.otherSO == parent2SO1_0);
                        Assert(comp0_1_0.otherComponent2 == parentComp1_0);
                        Assert(comp0_1_0.b == "instanceValue");

                        Assert(comp0_1.b == "instanceValue2");
                    }

                    Debug.Log("Passed stage 4.2");

                    // Load original scene and ensure instance modifications didn't influence it
                    {
                        EditorApplication.LoadScene("unitTest4Scene_1.prefab");

                        SceneObject parentSO0   = Scene.Root.FindChild("parentSO0", false);
                        SceneObject parentSO1   = Scene.Root.FindChild("parentSO1", false);
                        SceneObject parentSO1_0 = parentSO1.FindChild("parentSO1_0", false);

                        UT1_Component1 parentComp1_0 = parentSO1_0.GetComponent <UT1_Component1>();

                        SceneObject prefabInstance = parentSO0.GetChild(0);
                        SceneObject so0            = prefabInstance.FindChild("so0", false);
                        SceneObject so1            = prefabInstance.FindChild("so1_modified", false);
                        SceneObject so0_0          = so0.FindChild("so0_0", false);
                        SceneObject so0_1          = so0.FindChild("so0_1", false);
                        SceneObject so1_0          = so1.FindChild("so1_0", false);
                        SceneObject so0_1_0        = so0_1.FindChild("so0_1_0", false);
                        SceneObject so1_1          = so1.FindChild("so1_1", false);

                        UT1_Component1 comp0     = so0.GetComponent <UT1_Component1>();
                        UT1_Component1 comp1     = so1.GetComponent <UT1_Component1>();
                        UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>();
                        UT1_Component1 comp0_1   = so0_1.GetComponent <UT1_Component1>();

                        Assert(parentComp1_0.otherSO == so1_0);
                        Assert(parentComp1_0.otherComponent2 == comp0_1_0);
                        Assert(so1_1 != null);
                        Assert(so0_0 == null);
                        Assert(comp0 == null);
                        Assert(comp0_1_0.otherSO == so1_1);
                        Assert(comp0_1_0.otherComponent2 == comp1);
                        Assert(comp0_1_0.a == 123);
                        Assert(comp0_1_0.b == "modifiedValue");
                        Assert(comp1.otherSO == so1_0);
                        Assert(comp1.otherComponent2 == comp0_1_0);
                        Assert(comp0_1.b == "originalValue2");
                        Assert(MathEx.ApproxEquals(so1.LocalPosition.y, 999.0f));
                    }

                    Debug.Log("Passed stage 4.3");

                    // Modify prefab and ensure both prefab and instance modifications remain
                    {
                        // unitTest4Scene_0.prefab:
                        // so0 (Comp1)
                        //  - so0_1
                        //    - so0_1_0 (Comp1)
                        // so1 (Comp1, Comp2)
                        //  - so1_1
                        //  - so1_2 (Comp1)

                        // unitTest4Scene_0.prefab (unitTest4Scene_2.prefab instance):
                        // so0 (Comp1)
                        //  - so0_0
                        //  - so0_1 (Comp1)
                        //    - so0_1_0 (Comp1)
                        // so1 (Comp2)
                        //  - so1_2 (Comp1)

                        Scene.Load("unitTest4Scene_0.prefab");

                        SceneObject sceneRoot = Scene.Root;
                        SceneObject so0       = sceneRoot.FindChild("so0", false);
                        SceneObject so0_1     = so0.FindChild("so0_1", false);
                        SceneObject so1       = sceneRoot.FindChild("so1_modified", false);
                        SceneObject so1_0     = so1.FindChild("so1_0", false);
                        SceneObject so0_1_0   = so0_1.FindChild("so0_1_0", false);

                        SceneObject so1_2 = new SceneObject("so1_2");
                        so1_2.Parent = so1;

                        so0.AddComponent <UT1_Component1>();
                        so0_1.RemoveComponent <UT1_Component1>();
                        so1_0.Destroy();

                        UT1_Component1 comp3     = so1_2.AddComponent <UT1_Component1>();
                        UT1_Component1 comp0_1_0 = so0_1_0.GetComponent <UT1_Component1>();
                        comp0_1_0.b = "modifiedValueAgain";
                        so1.Name    = "so1_modifiedAgain";

                        comp3.otherSO         = so0_1;
                        comp3.otherComponent2 = comp0_1_0;

                        EditorApplication.SaveScene("unitTest4Scene_0.prefab");
                    }

                    Debug.Log("Passed stage 4.4");

                    // Reload the scene and ensure both instance and prefab modifications remain
                    {
                        EditorApplication.LoadScene("unitTest4Scene_2.prefab");

                        SceneObject root         = Scene.Root;
                        SceneObject parent2SO0   = root.FindChild("parent2SO0", false);
                        SceneObject parent2SO1   = root.FindChild("parent2SO1", false);
                        SceneObject parent2SO1_0 = parent2SO1.FindChild("parent2SO1_0", false);

                        SceneObject prefabInstance = parent2SO0.GetChild(0);

                        SceneObject so0     = prefabInstance.FindChild("so0", false);
                        SceneObject so1     = prefabInstance.FindChild("so1_modifiedAgain", false);
                        SceneObject so0_0   = so0.FindChild("so0_0", false);
                        SceneObject so0_1   = so0.FindChild("so0_1", false);
                        SceneObject so1_0   = so1.FindChild("so1_0", false);
                        SceneObject so1_1   = so1.FindChild("so1_1", false);
                        SceneObject so1_2   = so1.FindChild("so1_2", false);
                        SceneObject so0_1_0 = so0_1.FindChild("so0_1_0", false);

                        UT1_Component1 parentComp1_0 = parent2SO1_0.GetComponent <UT1_Component1>();
                        UT1_Component1 comp0         = so0.GetComponent <UT1_Component1>();
                        UT1_Component2 comp1         = so1.GetComponent <UT1_Component2>();
                        UT1_Component1 comp11        = so1.GetComponent <UT1_Component1>();
                        UT1_Component1 comp0_1_0     = so0_1_0.GetComponent <UT1_Component1>();
                        UT1_Component1 comp3         = so1_2.AddComponent <UT1_Component1>();

                        // Check instance modifications (they should override any prefab modifications)
                        Assert(so0_0 != null);
                        Assert(comp0 != null);
                        Assert(so1_1 == null);
                        Assert(comp11 == null);

                        Assert(comp0.otherSO == so0_1_0);
                        Assert(comp0.otherComponent == comp1);

                        Assert(parentComp1_0.otherSO == so1_0);
                        Assert(parentComp1_0.otherComponent2 == comp0_1_0);

                        Assert(comp0_1_0.otherSO == parent2SO1_0);
                        Assert(comp0_1_0.otherComponent2 == parentComp1_0);
                        Assert(comp0_1_0.b == "instanceValue");

                        // Check prefab modifications
                        Assert(so1_0 == null);
                        Assert(so1.Name == "so1_modifiedAgain");
                        Assert(comp3.otherSO == so0_1);
                        Assert(comp3.otherComponent2 == comp0_1_0);
                    }

                    Debug.Log("Passed stage 4.5");
                }
            }
            catch
            {
                PrintSceneState();

                throw;
            }
            finally
            {
                if (!string.IsNullOrEmpty(oldScene))
                {
                    Scene.Load(ProjectLibrary.GetPath(oldScene));
                }
                else
                {
                    Scene.Clear();
                }

                ProjectLibrary.Delete("unitTest4Scene_0.prefab");
                ProjectLibrary.Delete("unitTest4Scene_1.prefab");
                ProjectLibrary.Delete("unitTest4Scene_2.prefab");
            }
        }