Esempio n. 1
0
 public static bool IsOutside(AABB left, AABB right)
 {
     return	(left.MaxX - right.MinX) < 0 || (left.MinX - right.MaxX) > 0 ||
             (left.MaxY - right.MinY) < 0 || (left.MinY - right.MaxY) > 0 ||
             (left.MaxZ - right.MinZ) < 0 || (left.MinZ - right.MaxZ) > 0
                 ;
 }
Esempio n. 2
0
 public CSGMesh(Plane[] planes, List<Polygon> polygons, List<HalfEdge> edges, List<Vector3> vertices, AABB bounds)
 {
     this.Planes = planes;
     this.Polygons = polygons;
     this.Edges = edges;
     this.Vertices = vertices;
     this.Bounds.Set(bounds);
 }
Esempio n. 3
0
        public static CSGMesh Combine(Vector3 offset, IDictionary<CSGNode, CSGMesh> brushMeshes)
        {
            var planeLookup = new Dictionary<Plane, short>();
            var vertexLookup = new Dictionary<Vector3, short>();

            var planes = new List<Plane>();
            var polygons = new List<Polygon>();
            var edges = new List<HalfEdge>();
            var vertices = new List<Vector3>();

            var bounds = new AABB();

            bounds.Clear();
            int edgeIndex = 0;
            int polygonIndex = 0;
            foreach (var item in brushMeshes)
            {
                var node = item.Key;
                var translation = Vector3.Subtract(node.Translation, offset);
                var mesh = item.Value;
                foreach (var edge in mesh.Edges)
                {
                    short vertexIndex;
                    var vertex = Vector3.Add(mesh.Vertices[edge.VertexIndex], translation);
                    if (!vertexLookup.TryGetValue(vertex, out vertexIndex))
                    {
                        vertexIndex = (short)vertices.Count;
                        vertices.Add(vertex);
                        vertexLookup.Add(vertex, vertexIndex);
                    }

                    var newEdge = new HalfEdge();
                    newEdge.VertexIndex = vertexIndex;
                    newEdge.NextIndex = (short)(edge.NextIndex + edgeIndex);
                    newEdge.TwinIndex = (short)(edge.TwinIndex + edgeIndex);
                    newEdge.PolygonIndex = (short)(edge.PolygonIndex + polygonIndex);

                    edges.Add(newEdge);
                }

                foreach (var polygon in mesh.Polygons)
                {
                    if (polygon.FirstIndex == -1)
                        continue;
                    short planeIndex;
                    var plane = mesh.Planes[polygon.PlaneIndex];
                    if (!planeLookup.TryGetValue(plane, out planeIndex))
                    {
                        planeIndex = (short)planes.Count;
                        planes.Add(plane);
                        planeLookup.Add(plane, planeIndex);
                    }

                    var newPolygon = new Polygon();
                    newPolygon.PlaneIndex = planeIndex;
                    newPolygon.FirstIndex = (short)(polygon.FirstIndex + edgeIndex);
                    newPolygon.Category = polygon.Category;
                    newPolygon.Visible = polygon.Visible;
                    newPolygon.Bounds.Set(polygon.Bounds, translation);

                    polygons.Add(newPolygon);

                    if (newPolygon.Visible)
                    {
                        var first = edges[newPolygon.FirstIndex];
                        var iterator = first;
                        do
                        {
                            bounds.Add(vertices[iterator.VertexIndex]);
                            iterator = edges[iterator.NextIndex];
                        } while (iterator != first);
                    }
                }
                edgeIndex = edges.Count;
                polygonIndex = polygons.Count;
            }
            return new CSGMesh(planes.ToArray(), polygons, edges, vertices, bounds);
        }
Esempio n. 4
0
        public void Intersect(AABB		cuttingNodeBounds,
							  Plane[]	cuttingNodePlanes,
							  Vector3	cuttingNodeTranslation,
							  Vector3	inputPolygonTranslation,

							  List<Polygon> inputPolygons,

							  List<Polygon> inside,
							  List<Polygon> aligned,
							  List<Polygon> revAligned,
							  List<Polygon> outside)
        {
            var categories			= new PolygonSplitResult[cuttingNodePlanes.Length];
            var translatedPlanes	= new Plane[cuttingNodePlanes.Length];
            var translation			= Vector3.Subtract(cuttingNodeTranslation, inputPolygonTranslation);

            // translate the planes we cut our polygons with so that they're located at the same
            // relative distance from the polygons as the brushes are from each other.
            for (int i = 0; i < cuttingNodePlanes.Length; i++)
                translatedPlanes[i] = Plane.Translated(cuttingNodePlanes[i], translation);

            var vertices = this.Vertices;
            var edges = this.Edges;
            var planes = this.Planes;
            for (int i = inputPolygons.Count - 1; i >= 0; i--)
            {
                var inputPolygon = inputPolygons[i];
                if (inputPolygon.FirstIndex == -1)
                    continue;

                var bounds		= inputPolygon.Bounds;
                var finalResult = PolygonSplitResult.CompletelyInside;

                // A quick check if the polygon lies outside the planes we're cutting our polygons with.
                if (!AABB.IsOutside(cuttingNodeBounds, translation, bounds))
                {
                    PolygonSplitResult	intermediateResult;
                    Polygon				outsidePolygon = null;
                    for (int otherIndex = 0; otherIndex < translatedPlanes.Length; otherIndex++)
                    {
                        var translatedCuttingPlane = translatedPlanes[otherIndex];

                        var side = cuttingNodePlanes[otherIndex].OnSide(bounds, translation.Negated());
                        if (side == PlaneSideResult.Outside)
                        {
                            finalResult = PolygonSplitResult.CompletelyOutside;
                            break;	// nothing left to process, so we exit
                        } else
                        if (side == PlaneSideResult.Inside)
                            continue;

                        var polygon = inputPolygon;
                        intermediateResult = PolygonSplit(translatedCuttingPlane, inputPolygonTranslation, ref polygon, out outsidePolygon);
                        inputPolygon = polygon;

                        if (intermediateResult == PolygonSplitResult.CompletelyOutside)
                        {
                            finalResult = PolygonSplitResult.CompletelyOutside;
                            break;	// nothing left to process, so we exit
                        } else
                        if (intermediateResult == PolygonSplitResult.Split)
                        {
                            if (outside != null)
                                outside.Add(outsidePolygon);
                            // Note: left over is still completely inside,
                            //		 or plane (opposite) aligned
                        } else
                        if (intermediateResult != PolygonSplitResult.CompletelyInside)
                            finalResult = intermediateResult;
                    }
                } else
                    finalResult = PolygonSplitResult.CompletelyOutside;

                switch (finalResult)
                {
                    case PolygonSplitResult.CompletelyInside:		inside .Add(inputPolygon); break;
                    case PolygonSplitResult.CompletelyOutside:		outside.Add(inputPolygon); break;

                    // The polygon can only be visible if it's part of the last brush that shares it's surface area,
                    // otherwise we'd get overlapping polygons if two brushes overlap.
                    // When the (final) polygon is aligned with one of the cutting planes, we know it lies on the surface of
                    // the CSG node we're cutting the polygons with. We also know that this node is not the node this polygon belongs to
                    // because we've done that check earlier on. So we flag this polygon as being invisible.
                    case PolygonSplitResult.PlaneAligned:			inputPolygon.Visible = false; aligned   .Add(inputPolygon); break;
                    case PolygonSplitResult.PlaneOppositeAligned:	inputPolygon.Visible = false; revAligned.Add(inputPolygon); break;
                }
            }
        }
Esempio n. 5
0
        public CSGMesh Clone()
        {
            var newPlanes = new Plane[Planes.Length];
            for (int i = 0; i < Planes.Length; i++)
            {
                var plane = Planes[i];
                newPlanes[i] = new Plane(plane.A, plane.B, plane.C, plane.D);
            }
            var newPolygons = new List<Polygon>(Polygons.Count);
            foreach (var polygon in Polygons)
            {
                var newPolygon = new Polygon();
                newPolygon.FirstIndex = polygon.FirstIndex;
                newPolygon.Visible = polygon.Visible;
                newPolygon.Category = polygon.Category;
                newPolygon.PlaneIndex = polygon.PlaneIndex;
                newPolygon.Bounds.Set(polygon.Bounds);

                newPolygons.Add(newPolygon);
            }

            var newEdges = new List<HalfEdge>(Edges.Count);
            foreach (var edge in Edges)
            {
                var newEdge = new HalfEdge();
                newEdge.NextIndex = edge.NextIndex;
                newEdge.PolygonIndex = edge.PolygonIndex;
                newEdge.TwinIndex = edge.TwinIndex;
                newEdge.VertexIndex = edge.VertexIndex;
                newEdges.Add(newEdge);
            }

            var newVertices = new List<Vector3>(Vertices.Count);
            foreach (var vertex in Vertices)
            {
                var newVertex = new Vector3(vertex.X, vertex.Y, vertex.Z);
                newVertices.Add(newVertex);
            }

            var newBounds = new AABB(Bounds);
            var newMesh = new CSGMesh(
                newPlanes,
                newPolygons,
                newEdges,
                newVertices,
                newBounds);

            return newMesh;
        }
Esempio n. 6
0
        public static CSGMesh CreateFromPlanes(Plane[] brushPlanes)
        {
            var planes = new Plane[brushPlanes.Length];
            for (int i = 0; i < brushPlanes.Length; i++)
            {
                var plane = brushPlanes[i];
                planes[i] = new Plane(plane.A, plane.B, plane.C, plane.D);
            }

            var pointIntersections = new List<PointIntersection>(planes.Length * planes.Length);
            var intersectingPlanes = new List<short>();
            var vertices = new List<Vector3>();
            var edges = new List<HalfEdge>();

            // Find all point intersections where 3 (or more planes) intersect
            for (short planeIndex1 = 0; planeIndex1 < planes.Length - 2; planeIndex1++)
            {
                var plane1 = planes[planeIndex1];
                for (short planeIndex2 = (short)(planeIndex1 + 1); planeIndex2 < planes.Length - 1; planeIndex2++)
                {
                    var plane2 = planes[planeIndex2];
                    for (short planeIndex3 = (short)(planeIndex2 + 1); planeIndex3 < planes.Length; planeIndex3++)
                    {
                        var plane3 = planes[planeIndex3];

                        // Calculate the intersection
                        var vertex = Plane.Intersection(plane1, plane2, plane3);

                        // Check if the intersection is valid
                        if (float.IsNaN(vertex.X) || float.IsNaN(vertex.Y) || float.IsNaN(vertex.Z) ||
                            float.IsInfinity(vertex.X) || float.IsInfinity(vertex.Y) || float.IsInfinity(vertex.Z))
                            continue;

                        intersectingPlanes.Clear();
                        intersectingPlanes.Add(planeIndex1);
                        intersectingPlanes.Add(planeIndex2);
                        intersectingPlanes.Add(planeIndex3);

                        for (short planeIndex4 = 0; planeIndex4 < planes.Length; planeIndex4++)
                        {
                            if (planeIndex4 == planeIndex1 ||
                                planeIndex4 == planeIndex2 ||
                                planeIndex4 == planeIndex3)
                                continue;

                            var plane4 = planes[planeIndex4];
                            var side = plane4.OnSide(vertex);
                            if (side == PlaneSideResult.Intersects)
                            {
                                if (planeIndex4 < planeIndex3)
                                    // Already found this vertex
                                    goto SkipIntersection;

                                // We've found another plane which goes trough our found intersection point
                                intersectingPlanes.Add(planeIndex4);
                            }
                            else
                                if (side == PlaneSideResult.Outside)
                                    // Intersection is outside of brush
                                    goto SkipIntersection;
                        }

                        var vertexIndex = (short)vertices.Count;
                        vertices.Add(vertex);

                        // Add intersection point to our list
                        pointIntersections.Add(new PointIntersection(vertexIndex, intersectingPlanes));

                    SkipIntersection:
                        ;
                    }
                }
            }

            var foundPlanes = new short[2];
            // Find all our intersection edges which are formed by a pair of planes
            // (this could probably be done inside the previous loop)
            for (int i = 0; i < pointIntersections.Count; i++)
            {
                var pointIntersectionA = pointIntersections[i];
                for (int j = i + 1; j < pointIntersections.Count; j++)
                {
                    var pointIntersectionB = pointIntersections[j];
                    var planesIndicesA = pointIntersectionA.PlaneIndices;
                    var planesIndicesB = pointIntersectionB.PlaneIndices;

                    short foundPlaneIndex = 0;
                    foreach (var currentPlaneIndex in planesIndicesA)
                    {
                        if (!planesIndicesB.Contains(currentPlaneIndex))
                            continue;

                        foundPlanes[foundPlaneIndex] = currentPlaneIndex;
                        foundPlaneIndex++;

                        if (foundPlaneIndex == 2)
                            break;
                    }

                    // If foundPlaneIndex is 0 or 1 then either this combination does not exist,
                    // or only goes trough one point
                    if (foundPlaneIndex < 2)
                        continue;

                    // Create our found intersection edge
                    var halfEdgeA = new HalfEdge();
                    var halfEdgeAIndex = (short)edges.Count;
                    edges.Add(halfEdgeA);

                    var halfEdgeB = new HalfEdge();
                    var halfEdgeBIndex = (short)edges.Count;
                    edges.Add(halfEdgeB);

                    halfEdgeA.TwinIndex = halfEdgeBIndex;
                    halfEdgeB.TwinIndex = halfEdgeAIndex;

                    halfEdgeA.VertexIndex = pointIntersectionA.VertexIndex;
                    halfEdgeB.VertexIndex = pointIntersectionB.VertexIndex;

                    // Add it to our points
                    pointIntersectionA.Edges.Add(new EdgeIntersection(
                                                        halfEdgeA,
                                                        foundPlanes[0],
                                                        foundPlanes[1]));
                    pointIntersectionB.Edges.Add(new EdgeIntersection(
                                                        halfEdgeB,
                                                        foundPlanes[0],
                                                        foundPlanes[1]));
                }
            }

            var polygons = new List<Polygon>();
            for (short i = 0; i < (short)planes.Length; i++)
            {
                var polygon = new Polygon();
                polygon.PlaneIndex = i;
                polygons.Add(polygon);
            }

            var bounds = new AABB();
            var direction = new Vector3();
            for (int i = pointIntersections.Count - 1; i >= 0; i--)
            {
                var pointIntersection = pointIntersections[i];
                var pointEdges = pointIntersection.Edges;

                // Make sure that we have at least 2 edges ...
                // This may happen when a plane only intersects at a single edge.
                if (pointEdges.Count <= 2)
                {
                    pointIntersections.RemoveAt(i);
                    continue;
                }

                var vertexIndex = pointIntersection.VertexIndex;
                var vertex = vertices[vertexIndex];

                for (int j = 0; j < pointEdges.Count - 1; j++)
                {
                    var edge1 = pointEdges[j];
                    for (int k = j + 1; k < pointEdges.Count; k++)
                    {
                        var edge2 = pointEdges[k];

                        int planeIndex1 = -1;
                        int planeIndex2 = -1;

                        // Determine if and which of our 2 planes are identical
                        if (edge1.PlaneIndices[0] == edge2.PlaneIndices[0]) { planeIndex1 = 0; planeIndex2 = 0; }
                        else
                            if (edge1.PlaneIndices[0] == edge2.PlaneIndices[1]) { planeIndex1 = 0; planeIndex2 = 1; }
                            else
                                if (edge1.PlaneIndices[1] == edge2.PlaneIndices[0]) { planeIndex1 = 1; planeIndex2 = 0; }
                                else
                                    if (edge1.PlaneIndices[1] == edge2.PlaneIndices[1]) { planeIndex1 = 1; planeIndex2 = 1; }
                                    else
                                        continue;

                        HalfEdge ingoing;
                        HalfEdge outgoing;
                        short outgoingIndex;

                        var shared_plane = planes[edge1.PlaneIndices[planeIndex1]];
                        var edge1_plane = planes[edge1.PlaneIndices[1 - planeIndex1]];
                        var edge2_plane = planes[edge2.PlaneIndices[1 - planeIndex2]];

                        direction = Vector3.CrossProduct(shared_plane.Normal, edge1_plane.Normal);

                        // Determine the orientation of our two edges to determine
                        // which edge is in-going, and which one is out-going
                        if (Vector3.DotProduct(direction, edge2_plane.Normal) < 0)
                        {
                            ingoing = edge2.Edge;
                            outgoingIndex = edge1.Edge.TwinIndex;
                            outgoing = edges[outgoingIndex];
                        }
                        else
                        {
                            ingoing = edge1.Edge;
                            outgoingIndex = edge2.Edge.TwinIndex;
                            outgoing = edges[outgoingIndex];
                        }

                        // Link the out-going half-edge to the in-going half-edge
                        ingoing.NextIndex = outgoingIndex;

                        // Add reference to polygon to half-edge, and make sure our
                        // polygon has a reference to a half-edge
                        // Since a half-edge, in this case, serves as a circular
                        // linked list this just works.
                        var polygonIndex = edge1.PlaneIndices[planeIndex1];

                        ingoing.PolygonIndex = polygonIndex;
                        outgoing.PolygonIndex = polygonIndex;

                        var polygon = polygons[polygonIndex];
                        polygon.FirstIndex = outgoingIndex;
                        polygon.Bounds.Add(vertex);
                    }
                }

                // Add the intersection point to the area of our bounding box
                bounds.Add(vertex);
            }

            return new CSGMesh(planes, polygons, edges, vertices, bounds);
        }
Esempio n. 7
0
 public PlaneSideResult OnSide(AABB bounds, Vector3 translation)
 {
     var backward_x = A <= 0 ? bounds.MinX : bounds.MaxX;
     var backward_y = B <= 0 ? bounds.MinY : bounds.MaxY;
     var backward_z = C <= 0 ? bounds.MinZ : bounds.MaxZ;
     var distance = Distance(backward_x + translation.X, backward_y + translation.Y, backward_z + translation.Z);
     var side = OnSide(distance);
     if (side == PlaneSideResult.Inside)
         return PlaneSideResult.Inside;
     var forward_x = A >= 0 ? bounds.MinX : bounds.MaxX;
     var forward_y = B >= 0 ? bounds.MinY : bounds.MaxY;
     var forward_z = C >= 0 ? bounds.MinZ : bounds.MaxZ;
     distance = Distance(forward_x + translation.X, forward_y + translation.Y, forward_z + translation.Z);
     side = OnSide(distance);
     if (side == PlaneSideResult.Outside)
         return PlaneSideResult.Outside;
     return PlaneSideResult.Intersects;
 }
Esempio n. 8
0
 public PlaneSideResult OnSide(AABB bounds)
 {
     var x = A >= 0 ? bounds.MinX : bounds.MaxX;
     var y = B >= 0 ? bounds.MinY : bounds.MaxY;
     var z = C >= 0 ? bounds.MinZ : bounds.MaxZ;
     return OnSide(Distance(x, y, z));
 }
Esempio n. 9
0
 public static bool IsOutside(AABB left, Vector3 translation, AABB right)
 {
     return  ((left.MaxX + translation.X) - right.MinX) < 0 || ((left.MinX + translation.X) - right.MaxX) > 0 ||
             ((left.MaxY + translation.Y) - right.MinY) < 0 || ((left.MinY + translation.Y) - right.MaxY) > 0 ||
             ((left.MaxZ + translation.Z) - right.MinZ) < 0 || ((left.MinZ + translation.Z) - right.MaxZ) > 0
                 ;
 }
Esempio n. 10
0
        public void Set(AABB other, Vector3 translation)
        {
            this.MinX = (int)Math.Floor(other.MinX + translation.X);
            this.MinY = (int)Math.Floor(other.MinY + translation.Y);
            this.MinZ = (int)Math.Floor(other.MinZ + translation.Z);

            this.MaxX = (int)Math.Ceiling(other.MaxX + translation.X);
            this.MaxY = (int)Math.Ceiling(other.MaxY + translation.Y);
            this.MaxZ = (int)Math.Ceiling(other.MaxZ + translation.Z);
        }
Esempio n. 11
0
 public AABB(AABB other)
 {
     Clear();
     Set(other);
 }
Esempio n. 12
0
        public void Set(AABB bounds)
        {
            this.MinX = bounds.MinX;
            this.MinY = bounds.MinY;
            this.MinZ = bounds.MinZ;

            this.MaxX = bounds.MaxX;
            this.MaxY = bounds.MaxY;
            this.MaxZ = bounds.MaxZ;
        }
Esempio n. 13
0
 public bool IsOutside(AABB other)
 {
     return	(this.MaxX - other.MinX) < 0 || (this.MinX - other.MaxX) > 0 ||
             (this.MaxY - other.MinY) < 0 || (this.MinY - other.MaxY) > 0 ||
             (this.MaxZ - other.MinZ) < 0 || (this.MinZ - other.MaxZ) > 0
                 ;
 }
Esempio n. 14
0
        public void Add(AABB bounds)
        {
            MinX = Math.Min(MinX, bounds.MinX);
            MinY = Math.Min(MinY, bounds.MinY);
            MinZ = Math.Min(MinZ, bounds.MinZ);

            MaxX = Math.Max(MaxX, bounds.MaxX);
            MaxY = Math.Max(MaxY, bounds.MaxY);
            MaxZ = Math.Max(MaxZ, bounds.MaxZ);
        }
        public bool ValidateDrop(bool inSceneView)
        {
            if (!inSceneView)
            {
                return(false);
            }

            Reset();
            if (DragAndDrop.objectReferences == null ||
                DragAndDrop.objectReferences.Length == 0)
            {
                dragGameObjects = null;
                return(false);
            }

            dragGameObjects = new List <GameObject>();
            containsModel   = false;
            foreach (var obj in DragAndDrop.objectReferences)
            {
                var gameObject = obj as GameObject;
                if (gameObject == null)
                {
                    continue;
                }

                if (gameObject.GetComponentInChildren <CSGBrush>() == null)
                {
                    continue;
                }

                if (PrefabUtility.GetPrefabType(gameObject) == PrefabType.None)
                {
                    continue;
                }

                if (PrefabUtility.GetPrefabParent(gameObject) == null &&
                    PrefabUtility.GetPrefabObject(gameObject) != null)
                {
                    dragGameObjects.Add(gameObject);
                }

                containsModel = containsModel || (gameObject.GetComponent <CSGModel>() != null);
            }
            if (dragGameObjects.Count != 1)
            {
                dragGameObjects = null;
                return(false);
            }

            var dragGameObjectBounds = new AABB();

            dragGameObjectBounds.Reset();
            foreach (var gameObject in dragGameObjects)
            {
                var brushes = gameObject.GetComponentsInChildren <CSGBrush>();
                if (brushes.Length == 0)
                {
                    continue;
                }
                dragGameObjectBounds.Add(BoundsUtilities.GetLocalBounds(brushes, gameObject.transform.worldToLocalMatrix));
            }

            if (!dragGameObjectBounds.Valid)
            {
                dragGameObjectBounds.Extend(MathConstants.zeroVector3);
            }

            projectedBounds = new Vector3[8];
            BoundsUtilities.GetBoundsCornerPoints(dragGameObjectBounds, projectedBounds);
            haveNoParent = false;
            return(true);
        }
        public void SetBounds(AABB newBounds, bool autoAdjust = true)
        {
            var boundsSize = newBounds.Size;

            stairsWidth  = GeometryUtility.CleanLength(boundsSize.x);
            stairsHeight = GeometryUtility.CleanLength(boundsSize.y);
            stairsDepth  = GeometryUtility.CleanLength(boundsSize.z);

            if (float.IsInfinity(stairsWidth) || float.IsNaN(stairsWidth))
            {
                stairsWidth = 1.0f;
            }
            if (float.IsInfinity(stairsDepth) || float.IsNaN(stairsDepth))
            {
                stairsDepth = stepDepth;
            }
            if (float.IsInfinity(stairsHeight) || float.IsNaN(stairsHeight))
            {
                stairsHeight = stepHeight;
            }
            stairsWidth  = Mathf.Max(0, stairsWidth);
            stairsDepth  = Mathf.Max(0, stairsDepth);
            stairsHeight = Mathf.Max(0, stairsHeight);

            if (autoAdjust)
            {
                if (!bounds.IsEmpty())
                {
                    float offsetY = (newBounds.MaxY - bounds.MaxY) + (bounds.MinY - newBounds.MinY);
                    float offsetZ = (newBounds.MaxZ - bounds.MaxZ) + (bounds.MinZ - newBounds.MinZ);

                    if (offsetY != 0)                     // scaling in height direction
                    {
                        if (offsetY > 0)                  // growing
                        {
                            if (extraDepth > 0)
                            {
                                extraDepth = stairsDepth - ((Mathf.Max(0, stairsHeight - extraHeight) / stepHeight) * stepDepth);
                            }
                            else
                            {
                                extraDepth  -= offsetY;
                                extraHeight += offsetY;
                            }
                        }
                        else                           // shrinking
                        {
                            extraHeight += offsetY;
                            extraDepth   = stairsDepth - ((Mathf.Max(0, stairsHeight - extraHeight) / stepHeight) * stepDepth);
                        }
                    }

                    if (offsetZ != 0)                     // scaling in depth direction
                    {
                        if (offsetZ > 0)                  // growing
                        {
                            if (extraHeight > 0)
                            {
                                extraHeight = stairsHeight - ((Mathf.Max(0, stairsDepth - extraDepth) / stepDepth) * stepHeight);
                            }
                            else
                            {
                                extraDepth += offsetZ; if (extraDepth < 0)
                                {
                                    extraDepth = 0;
                                }
                                extraHeight -= offsetZ;
                            }
                        }
                        else                           // shrinking
                        {
                            if (extraDepth > 0)
                            {
                                extraDepth = Mathf.Max(0, extraDepth + offsetZ);
                            }
                            extraHeight = stairsHeight - ((Mathf.Max(0, stairsDepth - extraDepth) / stepDepth) * stepHeight);
                        }
                        extraDepth = stairsDepth - ((Mathf.Max(0, stairsHeight - extraHeight) / stepHeight) * stepDepth);
                    }

                    if (extraDepth < 0)
                    {
                        extraDepth = 0;
                    }
                    if (extraHeight < 0)
                    {
                        extraHeight = 0;
                    }
                }
                else
                {
                    extraDepth  = Mathf.Max(0, stairsDepth - (totalSteps * stepDepth));
                    extraHeight = 0;
                }
                CalcTotalSteps();

                if ((totalSteps * stepDepth) > stairsDepth)
                {
                    var newTotalSteps = Mathf.Max(1, Mathf.FloorToInt((stairsDepth - extraDepth) / stepDepth));
                    extraHeight += (totalSteps - newTotalSteps) * stepHeight;
                    totalSteps   = newTotalSteps;
                }
            }

            bounds = newBounds;
        }