Пример #1
0
        public void Validate()
        {
            if (surfaceDefinition == null)
            {
                surfaceDefinition = new ChiselSurfaceDefinition();
            }

            if (surfaceDefinition.EnsureSize((int)SurfaceSides.TotalSides))
            {
                var defaultRenderMaterial  = ChiselMaterialManager.DefaultWallMaterial;
                var defaultPhysicsMaterial = ChiselMaterialManager.DefaultPhysicsMaterial;

                surfaceDefinition.surfaces[(int)SurfaceSides.Top].brushMaterial    = ChiselBrushMaterial.CreateInstance(ChiselMaterialManager.DefaultFloorMaterial, defaultPhysicsMaterial);
                surfaceDefinition.surfaces[(int)SurfaceSides.Bottom].brushMaterial = ChiselBrushMaterial.CreateInstance(ChiselMaterialManager.DefaultFloorMaterial, defaultPhysicsMaterial);
                surfaceDefinition.surfaces[(int)SurfaceSides.Left].brushMaterial   = ChiselBrushMaterial.CreateInstance(ChiselMaterialManager.DefaultWallMaterial, defaultPhysicsMaterial);
                surfaceDefinition.surfaces[(int)SurfaceSides.Right].brushMaterial  = ChiselBrushMaterial.CreateInstance(ChiselMaterialManager.DefaultWallMaterial, defaultPhysicsMaterial);
                surfaceDefinition.surfaces[(int)SurfaceSides.Front].brushMaterial  = ChiselBrushMaterial.CreateInstance(ChiselMaterialManager.DefaultWallMaterial, defaultPhysicsMaterial);
                surfaceDefinition.surfaces[(int)SurfaceSides.Back].brushMaterial   = ChiselBrushMaterial.CreateInstance(ChiselMaterialManager.DefaultWallMaterial, defaultPhysicsMaterial);
                surfaceDefinition.surfaces[(int)SurfaceSides.Tread].brushMaterial  = ChiselBrushMaterial.CreateInstance(ChiselMaterialManager.DefaultTreadMaterial, defaultPhysicsMaterial);
                surfaceDefinition.surfaces[(int)SurfaceSides.Step].brushMaterial   = ChiselBrushMaterial.CreateInstance(ChiselMaterialManager.DefaultStepMaterial, defaultPhysicsMaterial);

                for (int i = 0; i < surfaceDefinition.surfaces.Length; i++)
                {
                    if (surfaceDefinition.surfaces[i].brushMaterial == null)
                    {
                        surfaceDefinition.surfaces[i].brushMaterial = ChiselBrushMaterial.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial);
                    }
                }
            }


            stepHeight  = Mathf.Max(kMinStepHeight, stepHeight);
            stepDepth   = Mathf.Clamp(stepDepth, kMinStepDepth, absDepth);
            treadHeight = Mathf.Max(0, treadHeight);
            nosingDepth = Mathf.Max(0, nosingDepth);
            nosingWidth = Mathf.Max(0, nosingWidth);

            width = Mathf.Max(kMinWidth, absWidth) * (width < 0 ? -1 : 1);
            depth = Mathf.Max(stepDepth, absDepth) * (depth < 0 ? -1 : 1);

            riserDepth = Mathf.Max(kMinRiserDepth, riserDepth);
            sideDepth  = Mathf.Max(0, sideDepth);
            sideWidth  = Mathf.Max(kMinSideWidth, sideWidth);
            sideHeight = Mathf.Max(0, sideHeight);

            var realHeight       = Mathf.Max(stepHeight, absHeight);
            var maxPlateauHeight = realHeight - stepHeight;

            plateauHeight = Mathf.Clamp(plateauHeight, 0, maxPlateauHeight);

            var totalSteps      = Mathf.Max(1, Mathf.FloorToInt((realHeight - plateauHeight + kStepSmudgeValue) / stepHeight));
            var totalStepHeight = totalSteps * stepHeight;

            plateauHeight = Mathf.Max(0, realHeight - totalStepHeight);
            stepDepth     = Mathf.Clamp(stepDepth, kMinStepDepth, absDepth / totalSteps);
        }
        public void Validate()
        {
            tubeWidth     = Mathf.Max(tubeWidth, kMinTubeDiameter);
            tubeHeight    = Mathf.Max(tubeHeight, kMinTubeDiameter);
            outerDiameter = Mathf.Max(outerDiameter, tubeWidth * 2);

            horizontalSegments = Mathf.Max(horizontalSegments, 3);
            verticalSegments   = Mathf.Max(verticalSegments, 3);

            totalAngle = Mathf.Clamp(totalAngle, 1, 360);                  // TODO: constants


            if (surfaceAssets == null ||
                surfaceAssets.Length != 6)
            {
                var defaultRenderMaterial  = CSGMaterialManager.DefaultWallMaterial;
                var defaultPhysicsMaterial = CSGMaterialManager.DefaultPhysicsMaterial;
                surfaceAssets = new CSGSurfaceAsset[6]
                {
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial),
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial),
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial),
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial),
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial),
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial)
                };
            }

            if (surfaceDescriptions == null ||
                surfaceDescriptions.Length != 6)
            {
                // TODO: make this independent on plane position somehow
                var surfaceFlags = CSGDefaults.SurfaceFlags;
                surfaceDescriptions = new SurfaceDescription[6]
                {
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    },
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    },
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    },
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    },
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    },
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    }
                };
            }
        }
Пример #3
0
        public Vector3 ClosestPoint(Vector3 point)
        {
            var closest = point - center;

            closest.x = Mathf.Clamp(closest.x, -extents.x, extents.x);
            closest.y = Mathf.Clamp(closest.y, -extents.y, extents.y);
            closest.z = Mathf.Clamp(closest.z, -extents.z, extents.z);
            closest  += center;
            return(closest);
        }
Пример #4
0
        public virtual void Reset(float lerpValue = 0f)
        {
            float time = 0f;
            float minTime1 = 0f, minTime2 = 0f;
            float maxTime1 = 0f, maxTime2 = 0f;

            switch (MyType)
            {
            case TimerType.CONST:
                CurrentTimeValue = Time1;
                break;

            case TimerType.LERP_TWO_CONSTANTS:
                CurrentTimeValue = Mathf.Lerp(Time1, Time2, lerpValue);
                break;

            case TimerType.RANDOM_TWO_CONSTANTS:
                CurrentTimeValue = Random.Range(Time1, Time2);
                break;

            case TimerType.LERP_RANDOM_FOUR_CONSTANTS:
                CurrentTimeValue = Random.Range(Mathf.Lerp(Time1, Time2, lerpValue), Mathf.Lerp(Time3, Time4, lerpValue));
                break;

            case TimerType.LERP_CURVE:
                CurrentTimeValue = Curve1.Evaluate(Mathf.Lerp(CurveMaxTime(ref Curve1), CurveMinTime(ref Curve1), lerpValue)) * ValueMultiplier;
                break;

            case TimerType.RANDOM_CURVE:
                CurrentTimeValue = Curve1.Evaluate(RandomCurveTime(ref Curve1)) * ValueMultiplier;
                break;

            case TimerType.LERP_RANDOM_TWO_CURVES:
                minTime1         = CurveMinTime(ref Curve1);
                maxTime1         = CurveMaxTime(ref Curve2);
                minTime2         = CurveMinTime(ref Curve2);
                maxTime2         = CurveMaxTime(ref Curve2);
                time             = Mathf.Clamp(Mathf.Lerp(minTime1, maxTime1, lerpValue), minTime2, maxTime2);
                CurrentTimeValue = Random.Range(Curve1.Evaluate(time), Curve2.Evaluate(time)) * ValueMultiplier;
                break;

            case TimerType.RANDOM_TWO_CURVES:
                time             = RandomCurveTime(ref Curve1);
                minTime2         = CurveMinTime(ref Curve2);
                maxTime2         = CurveMaxTime(ref Curve2);
                time             = Mathf.Clamp(time, minTime2, maxTime2);
                CurrentTimeValue = Random.Range(Curve1.Evaluate(time), Curve2.Evaluate(time)) * ValueMultiplier;
                break;

            default:
                CurrentTimeValue = Time1;
                break;
            }
            timer = 0f;
        }
Пример #5
0
        public void Validate()
        {
            curveSegments   = Mathf.Max(curveSegments, 2);
            revolveSegments = Mathf.Max(revolveSegments, 1);

            totalAngle = Mathf.Clamp(totalAngle, 1, 360);                  // TODO: constants

            if (surfaceAssets == null ||
                surfaceAssets.Length != 6)
            {
                var defaultRenderMaterial  = CSGMaterialManager.DefaultWallMaterial;
                var defaultPhysicsMaterial = CSGMaterialManager.DefaultPhysicsMaterial;
                surfaceAssets = new CSGSurfaceAsset[6]
                {
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial),
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial),
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial),
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial),
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial),
                    CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial)
                };
            }

            if (surfaceDescriptions == null ||
                surfaceDescriptions.Length != 6)
            {
                // TODO: make this independent on plane position somehow
                var surfaceFlags = CSGDefaults.SurfaceFlags;
                surfaceDescriptions = new SurfaceDescription[6]
                {
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    },
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    },
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    },
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    },
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    },
                    new SurfaceDescription {
                        UV0 = UVMatrix.centered, surfaceFlags = surfaceFlags, smoothingGroup = 0
                    }
                };
            }
        }
Пример #6
0
        public void Validate()
        {
            if (surfaceDefinition == null)
            {
                surfaceDefinition = new ChiselSurfaceDefinition();
            }

            curveSegments   = Mathf.Max(curveSegments, 2);
            revolveSegments = Mathf.Max(revolveSegments, 1);

            totalAngle = Mathf.Clamp(totalAngle, 1, 360);                  // TODO: constants

            surfaceDefinition.EnsureSize(6);
        }
Пример #7
0
        /** Clamps the velocity to the max speed and optionally the forwards direction.
         * \param velocity Desired velocity of the character. In world units per second.
         * \param maxSpeed Max speed of the character. In world units per second.
         * \param slowdownFactor Value between 0 and 1 which determines how much slower the character should move than normal.
         *      Normally 1 but should go to 0 when the character approaches the end of the path.
         * \param slowWhenNotFacingTarget Prevent the velocity from being too far away from the forward direction of the character
         *      and slow the character down if the desired velocity is not in the same direction as the forward vector.
         * \param forward Forward direction of the character. Used together with the \a slowWhenNotFacingTarget parameter.
         *
         * Note that all vectors are 2D vectors, not 3D vectors.
         *
         * \returns The clamped velocity in world units per second.
         */
        public static Vector2 ClampVelocity(Vector2 velocity, float maxSpeed, float slowdownFactor, bool slowWhenNotFacingTarget, Vector2 forward)
        {
            // Max speed to use for this frame
            var currentMaxSpeed = maxSpeed * slowdownFactor;

            // Check if the agent should slow down in case it is not facing the direction it wants to move in
            if (slowWhenNotFacingTarget && (forward.x != 0 || forward.y != 0))
            {
                float currentSpeed;
                var   normalizedVelocity = VectorMath.Normalize(velocity.ToPFV2(), out currentSpeed);
                float dot = Vector2.Dot(normalizedVelocity.ToUnityV2(), forward);

                // Lower the speed when the character's forward direction is not pointing towards the desired velocity
                // 1 when velocity is in the same direction as forward
                // 0.2 when they point in the opposite directions
                float directionSpeedFactor = Mathf.Clamp(dot + 0.707f, 0.2f, 1.0f);
                currentMaxSpeed *= directionSpeedFactor;
                currentSpeed     = Mathf.Min(currentSpeed, currentMaxSpeed);

                // Angle between the forwards direction of the character and our desired velocity
                float angle = Mathf.Acos(Mathf.Clamp(dot, -1, 1));

                // Clamp the angle to 20 degrees
                // We cannot keep the velocity exactly in the forwards direction of the character
                // because we use the rotation to determine in which direction to rotate and if
                // the velocity would always be in the forwards direction of the character then
                // the character would never rotate.
                // Allow larger angles when near the end of the path to prevent oscillations.
                angle = Mathf.Min(angle, (20f + 180f * (1 - slowdownFactor * slowdownFactor)) * Mathf.Deg2Rad);

                float sin = Mathf.Sin(angle);
                float cos = Mathf.Cos(angle);

                // Determine if we should rotate clockwise or counter-clockwise to move towards the current velocity
                sin *= Mathf.Sign(normalizedVelocity.x * forward.y - normalizedVelocity.y * forward.x);
                // Rotate the #forward vector by #angle radians
                // The rotation is done using an inlined rotation matrix.
                // See https://en.wikipedia.org/wiki/Rotation_matrix
                return(new Vector2(forward.x * cos + forward.y * sin, forward.y * cos - forward.x * sin) * currentSpeed);
            }
            else
            {
                return(Vector2.ClampMagnitude(velocity, currentMaxSpeed));
            }
        }
        public void Validate()
        {
            if (surfaceDefinition == null)
            {
                surfaceDefinition = new ChiselSurfaceDefinition();
            }

            tubeWidth     = Mathf.Max(tubeWidth, kMinTubeDiameter);
            tubeHeight    = Mathf.Max(tubeHeight, kMinTubeDiameter);
            outerDiameter = Mathf.Max(outerDiameter, tubeWidth * 2);

            horizontalSegments = Mathf.Max(horizontalSegments, 3);
            verticalSegments   = Mathf.Max(verticalSegments, 3);

            totalAngle = Mathf.Clamp(totalAngle, 1, 360);                  // TODO: constants

            surfaceDefinition.EnsureSize(6);
        }
Пример #9
0
        public Block GetBlock(int x, int y, int z)
        {
            if (!deactivated)
            {
                //if (IsInBounds(x, y, z)) {
                x = Mathf.Clamp(x, 0, ChunkSizeX);
                y = Mathf.Clamp(y, 0, ChunkSizeY);
                z = Mathf.Clamp(z, 0, ChunkSizeZ);

                int index = Get_Flat_Index(x, y, z);

                Block res = new Block(blocks_type[index], blocks_iso[index]);
                res.set = blocks_set[index];

                return(res);
            }
            return(default(Block));
        }
        public void Validate()
        {
            if (surfaceAssets == null ||
                surfaceDescriptions.Length != (int)SurfaceSides.TotalSides)
            {
                var defaultRenderMaterial  = CSGMaterialManager.DefaultWallMaterial;
                var defaultPhysicsMaterial = CSGMaterialManager.DefaultPhysicsMaterial;
                surfaceAssets = new CSGSurfaceAsset[(int)SurfaceSides.TotalSides];

                surfaceAssets[(int)SurfaceSides.Top]     = CSGSurfaceAsset.CreateInstance(CSGMaterialManager.DefaultFloorMaterial, defaultPhysicsMaterial);
                surfaceAssets[(int)SurfaceSides.Bottom]  = CSGSurfaceAsset.CreateInstance(CSGMaterialManager.DefaultFloorMaterial, defaultPhysicsMaterial);
                surfaceAssets[(int)SurfaceSides.Left]    = CSGSurfaceAsset.CreateInstance(CSGMaterialManager.DefaultWallMaterial, defaultPhysicsMaterial);
                surfaceAssets[(int)SurfaceSides.Right]   = CSGSurfaceAsset.CreateInstance(CSGMaterialManager.DefaultWallMaterial, defaultPhysicsMaterial);
                surfaceAssets[(int)SurfaceSides.Forward] = CSGSurfaceAsset.CreateInstance(CSGMaterialManager.DefaultWallMaterial, defaultPhysicsMaterial);
                surfaceAssets[(int)SurfaceSides.Back]    = CSGSurfaceAsset.CreateInstance(CSGMaterialManager.DefaultWallMaterial, defaultPhysicsMaterial);
                surfaceAssets[(int)SurfaceSides.Tread]   = CSGSurfaceAsset.CreateInstance(CSGMaterialManager.DefaultTreadMaterial, defaultPhysicsMaterial);
                surfaceAssets[(int)SurfaceSides.Step]    = CSGSurfaceAsset.CreateInstance(CSGMaterialManager.DefaultStepMaterial, defaultPhysicsMaterial);

                for (int i = 0; i < surfaceAssets.Length; i++)
                {
                    if (surfaceAssets[i] == null)
                    {
                        surfaceAssets[i] = CSGSurfaceAsset.CreateInstance(defaultRenderMaterial, defaultPhysicsMaterial);
                    }
                }

                topSurface     = surfaceAssets[(int)SurfaceSides.Top];
                bottomSurface  = surfaceAssets[(int)SurfaceSides.Bottom];
                leftSurface    = surfaceAssets[(int)SurfaceSides.Left];
                rightSurface   = surfaceAssets[(int)SurfaceSides.Right];
                forwardSurface = surfaceAssets[(int)SurfaceSides.Forward];
                backSurface    = surfaceAssets[(int)SurfaceSides.Back];
                treadSurface   = surfaceAssets[(int)SurfaceSides.Tread];
                stepSurface    = surfaceAssets[(int)SurfaceSides.Step];
            }

            if (surfaceDescriptions == null ||
                surfaceDescriptions.Length != 6)
            {
                var surfaceFlags = CSGDefaults.SurfaceFlags;
                surfaceDescriptions = new SurfaceDescription[6];
                for (int i = 0; i < 6; i++)
                {
                    surfaceDescriptions[i] = new SurfaceDescription {
                        surfaceFlags = surfaceFlags, UV0 = UVMatrix.centered
                    };
                }
            }


            stepHeight  = Mathf.Max(kMinStepHeight, stepHeight);
            stepDepth   = Mathf.Clamp(stepDepth, kMinStepDepth, Mathf.Abs(depth));
            treadHeight = Mathf.Max(0, treadHeight);
            nosingDepth = Mathf.Max(0, nosingDepth);
            nosingWidth = Mathf.Max(0, nosingWidth);

            width = Mathf.Max(kMinWidth, Mathf.Abs(width)) * (width < 0 ? -1 : 1);
            depth = Mathf.Max(stepDepth, Mathf.Abs(depth)) * (depth < 0 ? -1 : 1);

            riserDepth = Mathf.Max(kMinRiserDepth, riserDepth);
            sideDepth  = Mathf.Max(0, sideDepth);
            sideWidth  = Mathf.Max(kMinSideWidth, sideWidth);
            sideHeight = Mathf.Max(0, sideHeight);

            var absHeight        = Mathf.Max(stepHeight, Mathf.Abs(height));
            var maxPlateauHeight = absHeight - stepHeight;

            plateauHeight = Mathf.Clamp(plateauHeight, 0, maxPlateauHeight);

            var totalSteps      = Mathf.Max(1, Mathf.FloorToInt((absHeight - plateauHeight) / stepHeight));
            var totalStepHeight = totalSteps * stepHeight;

            plateauHeight = Mathf.Max(0, absHeight - totalStepHeight);
            stepDepth     = Mathf.Clamp(stepDepth, kMinStepDepth, Mathf.Abs(depth) / totalSteps);
        }
Пример #11
0
        /// <summary>
        /// Fill a texture with a signed distance field generated from the alpha channel of a source texture.
        /// </summary>
        /// <param name="source">
        /// Source texture. Alpha values of 1 are considered inside, values of 0 are considered outside, and any other values are considered
        /// to be on the edge. Must be readable.
        /// </param>
        /// <param name="destination">
        /// Destination texture. Must be the same size as the source texture. Must be readable.
        /// The texture change does not get applied automatically, you need to do that yourself.
        /// </param>
        /// <param name="maxInside">
        /// Maximum pixel distance measured inside the edge, resulting in an alpha value of 1.
        /// If set to or below 0, everything inside will have an alpha value of 1.
        /// </param>
        /// <param name="maxOutside">
        /// Maximum pixel distance measured outside the edge, resulting in an alpha value of 0.
        /// If set to or below 0, everything outside will have an alpha value of 0.
        /// </param>
        /// <param name="postProcessDistance">
        /// Pixel distance from the edge within which pixels will be post-processed using the edge gradient.
        /// </param>
        /// <param name="rgbMode">
        /// How to fill the destination texture's RGB channels.
        /// </param>
        public static void Generate(
            Image <Rgba32> source,
            Image <Rgba32> destination,
            float maxInside,
            float maxOutside,
            float postProcessDistance,
            RGBFillMode rgbMode)
        {
            if (source.Height != destination.Height || source.Width != destination.Width)
            {
                Console.Out.WriteLine("Source and destination textures must be the same size.");
                return;
            }

            width  = source.Width;
            height = source.Height;
            pixels = new Pixel[width, height];
            int    x, y;
            float  scale;
            Rgba32 c = rgbMode == RGBFillMode.White ? Color.White: Color.Black;

            for (y = 0; y < height; y++)
            {
                for (x = 0; x < width; x++)
                {
                    pixels[x, y] = new Pixel();
                }
            }
            if (maxInside > 0f)
            {
                for (y = 0; y < height; y++)
                {
                    for (x = 0; x < width; x++)
                    {
                        pixels[x, y].alpha = source.GetPixel(x, y).R / 255f;
                    }
                }
                ComputeEdgeGradients();
                GenerateDistanceTransform();
                if (postProcessDistance > 0f)
                {
                    PostProcess(postProcessDistance);
                }
                scale = 1f / maxInside;
                for (y = 0; y < height; y++)
                {
                    for (x = 0; x < width; x++)
                    {
                        c.A = (byte)(255f * Math.Clamp(pixels[x, y].distance * scale, 0, 1));
                        destination.SetPixel(x, y, c);
                    }
                }
            }
            if (maxOutside > 0f)
            {
                for (y = 0; y < height; y++)
                {
                    for (x = 0; x < width; x++)
                    {
                        pixels[x, y].alpha = 1f - source.GetPixel(x, y).R / 255f;
                    }
                }
                ComputeEdgeGradients();
                GenerateDistanceTransform();
                if (postProcessDistance > 0f)
                {
                    PostProcess(postProcessDistance);
                }
                scale = 1f / maxOutside;
                if (maxInside > 0f)
                {
                    for (y = 0; y < height; y++)
                    {
                        for (x = 0; x < width; x++)
                        {
                            c.A = (byte)(255f * (0.5f + (destination.GetPixel(x, y).A / 255f -
                                                         Math.Clamp(pixels[x, y].distance * scale, 0, 1)) * 0.5f));
                            destination.SetPixel(x, y, c);
                        }
                    }
                }
                else
                {
                    for (y = 0; y < height; y++)
                    {
                        for (x = 0; x < width; x++)
                        {
                            c.A = (byte)(255f * Math.Clamp(1f - pixels[x, y].distance * scale, 0, 1));
                            destination.SetPixel(x, y, c);
                        }
                    }
                }
            }

            if (rgbMode == RGBFillMode.Distance)
            {
                for (y = 0; y < height; y++)
                {
                    for (x = 0; x < width; x++)
                    {
                        c   = destination.GetPixel(x, y);
                        c.R = c.A;
                        c.G = c.A;
                        c.B = c.A;
                        destination.SetPixel(x, y, c);
                    }
                }
            }
            else if (rgbMode == RGBFillMode.Source)
            {
                for (y = 0; y < height; y++)
                {
                    for (x = 0; x < width; x++)
                    {
                        c   = source.GetPixel(x, y);
                        c.A = destination.GetPixel(x, y).A;
                        destination.SetPixel(x, y, c);
                    }
                }
            }
            pixels = null;
        }
Пример #12
0
        public static bool Intersect(CubeXCollider src, CylinderXCollider dst, out XContact?contact)
        {
            var     invP = Quaternion.Inverse(src.Quaternion) * (dst.Position - src.Position);
            Vector3 n    = Vector3.zero;

            n.x = invP.x;
            n.z = invP.z;

            var extents = src.Size * 0.5f;
            var halfHa  = extents.y;
            var topA    = halfHa;
            var bottomA = -halfHa;
            var halfHb  = dst.Height * 0.5f;
            var topB    = invP.y + halfHb;
            var bottomB = invP.y - halfHb;

            var space    = dst.Radius;
            var sqrSpace = space * space;

            // 相撞时,俯视图下的圆和矩形必然相交
            var closest = n;

            closest.x = Mathf.Clamp(closest.x, -extents.x, extents.x);
            closest.z = Mathf.Clamp(closest.z, -extents.z, extents.z);

            if ((n - closest).sqrMagnitude > sqrSpace)
            {
                contact = null;
                return(false);
            }

            // 处理相交的情况
            Vector3 normal;
            float   penetration;
            float   verticalP = float.PositiveInfinity;
            float   horizontalP;

            var inside = false;

            if (n == closest)
            {
                inside = true;
                var disX = extents.x - Mathf.Abs(n.x);
                var disZ = extents.z - Mathf.Abs(n.z);
                //找到最近的一个面
                if (disX < disZ)
                {
                    // 沿X轴
                    if (n.x > 0)
                    {
                        closest.x = extents.x;
                    }
                    else
                    {
                        closest.x = -extents.x;
                    }
                }
                else
                {
                    // 沿Z轴
                    if (n.z > 0)
                    {
                        closest.z = extents.z;
                    }
                    else
                    {
                        closest.z = -extents.z;
                    }
                }
                horizontalP = space + (n - closest).magnitude;
            }
            else
            {
                horizontalP = space - (n - closest).magnitude;
            }

            if (Mathf.Sign(topA - topB) != Mathf.Sign(bottomB - bottomA))
            {
                // 斜向相撞
                if (topB > topA)
                {
                    verticalP = topA - bottomB;
                }
                else
                {
                    verticalP = topB - bottomA;
                }
            }

            if (horizontalP < verticalP)
            {
                normal = (src.Quaternion * (n - closest)).normalized;
                if (inside)
                {
                    normal = -normal;
                }
                penetration = horizontalP;
            }
            else
            {
                normal      = topB > topA ? Vector3.up : Vector3.down;
                penetration = verticalP;
            }
            if (normal == Vector3.zero)
            {
                normal = Vector3.up;
            }
            contact = new XContact(src, dst, normal, penetration);
            return(true);
        }
Пример #13
0
        public static bool Intersect(CubeXCollider src, SphereXCollider dst, out XContact?contact)
        {
            // 反向旋转sphere的位置,使得可以在cube的坐标系下进行碰撞判断
            var extents = src.Size * 0.5f;
            var invQ    = Quaternion.Inverse(src.Quaternion);
            var invP    = invQ * (dst.Position - src.Position);

            // 以下所有操作都是在cube的坐标系下,随后的实际方向需要进行坐标系转换
            var n       = invP;
            var closest = n;

            closest.x = Mathf.Clamp(closest.x, -extents.x, extents.x);
            closest.y = Mathf.Clamp(closest.y, -extents.y, extents.y);
            closest.z = Mathf.Clamp(closest.z, -extents.z, extents.z);
            var inside = false;

            if (n == closest)
            {
                inside = true;
                var disX = extents.x - Mathf.Abs(n.x);
                var disY = extents.y - Mathf.Abs(n.y);
                var disZ = extents.z - Mathf.Abs(n.z);
                //找到最近的一个面
                if (disX < disY && disX < disZ)
                {
                    // 沿X轴
                    if (n.x > 0)
                    {
                        closest.x = extents.x;
                    }
                    else
                    {
                        closest.x = -extents.x;
                    }
                }
                else if (disY < disX && disY < disZ)
                {
                    // 沿Y轴
                    if (n.y > 0)
                    {
                        closest.y = extents.y;
                    }
                    else
                    {
                        closest.y = -extents.y;
                    }
                }
                else
                {
                    // 沿Z轴
                    if (n.z > 0)
                    {
                        closest.z = extents.z;
                    }
                    else
                    {
                        closest.z = -extents.z;
                    }
                }
            }
            var dir      = n - closest;
            var sqrDist  = dir.sqrMagnitude;
            var space    = dst.Radius;
            var sqrSpace = space * space;

            if (sqrDist < sqrSpace || inside)
            {
                var dist        = Mathf.Sqrt(sqrDist);
                var normal      = (src.Quaternion * dir).normalized;
                var penetration = space - dist;
                if (inside)
                {
                    normal      = -normal;
                    penetration = space + dist;
                }
                if (normal == Vector3.zero)
                {
                    normal = Vector3.up;
                }
                contact = new XContact(src, dst, normal, penetration);
                return(true);
            }
            contact = null;
            return(false);
        }
Пример #14
0
        public static bool Intersect(CylinderXCollider src, SphereXCollider dst, out XContact?contact)
        {
            var n = dst.Position - src.Position;

            Vector3 closest = n;
            var     extents = src.bounds.Extents;

            closest.x = Mathf.Clamp(closest.x, -extents.x, extents.x);
            closest.y = Mathf.Clamp(closest.y, -extents.y, extents.y);
            closest.z = Mathf.Clamp(closest.z, -extents.z, extents.z);

            var v = new Vector2(closest.x, closest.z);

            if (v.sqrMagnitude > src.Radius * src.Radius)
            {
                v         = v.normalized * src.Radius;
                closest.x = v.x;
                closest.z = v.y;
            }

            bool inside = false;

            if (n == closest)
            {
                inside = true;
                // 往上下移动的最短距离
                var dist1 = extents.y - Mathf.Abs(closest.y);
                // 往水平四周移动的最短距离
                var dist2 = src.Radius - v.magnitude;

                if (dist1 < dist2)
                {
                    closest.y = closest.y > 0 ? extents.y : -extents.y;
                }
                else
                {
                    v         = v.normalized * src.Radius;
                    closest.x = v.x;
                    closest.z = v.y;
                }
            }

            var dir      = n - closest;
            var sqrDist  = dir.sqrMagnitude;
            var space    = dst.Radius;
            var sqrSpace = space * space;

            if (sqrDist < sqrSpace || inside)
            {
                var dist        = Mathf.Sqrt(sqrDist);
                var normal      = dir.normalized;
                var penetration = space - dist;
                if (inside)
                {
                    normal      = -normal;
                    penetration = space + dist;
                }
                if (normal == Vector3.zero)
                {
                    normal = Vector3.up;
                }
                contact = new XContact(src, dst, normal, penetration);
                return(true);
            }
            contact = null;
            return(false);
        }
Пример #15
0
        static void ProcessSingleFile(string[] args, int outlineThreshold)
        {
            if (args.Length < 3)
            {
                Logger.WriteErrorLine(
                    "Provide three arguments: [mode] [Input.png/jpg] [max colors] <output path replace from> <output path replace to>");
            }

            var mode          = args[0];
            var startFileName = args[1]
                                .Replace('/', Path.DirectorySeparatorChar)
                                .Replace('\\', Path.DirectorySeparatorChar);

            int.TryParse(args[2], out var maxColor);
            maxColor = Math.Clamp(maxColor, 3, 100);

            if (args.Length >= 4)
            {
                outputPathReplaceFrom = args[3];
            }

            if (args.Length >= 5)
            {
                outputPathReplaceTo = args[4];
            }

            outputNewFileName = args.Length >= 6 ? args[5] : null;

            if (mode == "otb")
            {
                ExecuteOutlineToBlack(startFileName, outlineThreshold);
            }
            else if (mode == "fsnb")
            {
                var otbFileName = ExecuteOutlineToBlack(startFileName, outlineThreshold);
                ExecuteFillSmallNotBlack(otbFileName);
            }
            else if (mode == "q")
            {
                ExecuteOutlineToBlack(startFileName, outlineThreshold);
                ExecuteQuantize(startFileName, maxColor);
            }
            else if (mode == "fots")
            {
                var otbFileName  = ExecuteOutlineToBlack(startFileName, outlineThreshold);
                var fsnbFileName = ExecuteFillSmallNotBlack(otbFileName);
                var qFileName    = ExecuteQuantize(startFileName, maxColor);
                ExecuteFlattenedOutlineToSource(qFileName, fsnbFileName);
            }
            else if (mode == "di")
            {
                var otbFileName  = ExecuteOutlineToBlack(startFileName, outlineThreshold);
                var fsnbFileName = ExecuteFillSmallNotBlack(otbFileName);
                var qFileName    = ExecuteQuantize(startFileName, maxColor);
                var fotsFileName = ExecuteFlattenedOutlineToSource(qFileName, fsnbFileName);
                ExecuteDetermineIsland(fotsFileName, startFileName);
            }
            else if (mode == "dit")
            {
                var rasapFileName = ExecuteResizeAndSaveAsPng(startFileName, 1500);
                var otbFileName   = ExecuteOutlineToBlack(rasapFileName, outlineThreshold);
                var fsnbFileName  = ExecuteFillSmallNotBlack(otbFileName);
                var qFileName     = ExecuteQuantize(rasapFileName, maxColor);
                var fotsFileName  = ExecuteFlattenedOutlineToSource(qFileName, fsnbFileName);
                var bytesFileName = ExecuteDetermineIsland(fotsFileName, rasapFileName);

                // 색칠 테스트. 첫 데이터를 얻기 위한 것으로 오류 메시지는 경고로 무시해도 된다.
                var ditFileName = ExecuteDetermineIslandTest(fsnbFileName, bytesFileName, true, false);

                // DIT 파일이 최종 컬러링 완성 시의 이미지이다. 이걸로 최종적으로 데이터를 다시 뽑는다.
                bytesFileName = ExecuteDetermineIsland(ditFileName, rasapFileName);

                //두 번째 테스트. 여기서 오류가 나면 뭔가 이상한거다.
                ExecuteDetermineIslandTest(fsnbFileName, bytesFileName, false, true);

                var bbFileName = ExecuteBoxBlur(fsnbFileName, 1);
                ExecuteSdf(bbFileName);

                // 필요없는 파일은 삭제한다.
                // 디버그가 필요한 경우 삭제하지 않고 살펴보면 된다.
//                if (rasapFileName != startFileName)
//                {
//                    File.Delete(rasapFileName);
//                }

                var artifactsDirName = Path.GetDirectoryName(ChangeToArtifactsPath(bbFileName));
                if (string.IsNullOrEmpty(artifactsDirName))
                {
                    throw  new ArgumentNullException();
                }

                try
                {
                    Directory.Delete(artifactsDirName, true);
                }
                catch (DirectoryNotFoundException)
                {
                }

                MoveOrOverwriteToArtifactsDirectory(bbFileName);
                MoveOrOverwriteToArtifactsDirectory(ditFileName);
                MoveOrOverwriteToArtifactsDirectory(fotsFileName);
                MoveOrOverwriteToArtifactsDirectory(fsnbFileName);
                MoveOrOverwriteToArtifactsDirectory(otbFileName);
                MoveOrOverwriteToArtifactsDirectory(qFileName);
                MoveOrOverwriteToArtifactsDirectory(rasapFileName);
            }
            else
            {
                Logger.WriteErrorLine($"Unknown mode provided: {mode}");
                return;
            }

            Logger.WriteLine("Completed.");
        }
Пример #16
0
 static void SetPixelClamped(Image <Rgba32> image, int x, int y, Rgba32 v)
 {
     image[Math.Clamp(x, 0, image.Width - 1), Math.Clamp(y, 0, image.Height - 1)] = v;
 }
Пример #17
0
 static Rgba32 GetPixelClamped(Image <Rgba32> image, int x, int y)
 {
     return(image[Math.Clamp(x, 0, image.Width - 1), Math.Clamp(y, 0, image.Height - 1)]);
 }
Пример #18
0
        static void ProcessSingleFile(string[] args, int outlineThreshold)
        {
            if (args.Length < 3)
            {
                Console.Out.WriteLine("Provide three arguments: [mode] [Input.png/jpg] [max colors] <output path replace from> <output path replace to>");
            }

            var mode          = args[0];
            var startFileName = args[1]
                                .Replace('/', Path.DirectorySeparatorChar)
                                .Replace('\\', Path.DirectorySeparatorChar);

            int.TryParse(args[2], out var maxColor);
            maxColor = Math.Clamp(maxColor, 3, 100);

            if (args.Length >= 4)
            {
                outputPathReplaceFrom = args[3];
            }

            if (args.Length >= 5)
            {
                outputPathReplaceTo = args[4];
            }

            if (mode == "otb")
            {
                ExecuteOutlineToBlack(startFileName, outlineThreshold);
            }
            else if (mode == "fsnb")
            {
                var otbFileName = ExecuteOutlineToBlack(startFileName, outlineThreshold);
                ExecuteFillSmallNotBlack(otbFileName);
            }
            else if (mode == "q")
            {
                ExecuteOutlineToBlack(startFileName, outlineThreshold);
                ExecuteQuantize(startFileName, maxColor);
            }
            else if (mode == "fots")
            {
                var otbFileName  = ExecuteOutlineToBlack(startFileName, outlineThreshold);
                var fsnbFileName = ExecuteFillSmallNotBlack(otbFileName);
                var qFileName    = ExecuteQuantize(startFileName, maxColor);
                ExecuteFlattenedOutlineToSource(qFileName, fsnbFileName);
            }
            else if (mode == "di")
            {
                var otbFileName  = ExecuteOutlineToBlack(startFileName, outlineThreshold);
                var fsnbFileName = ExecuteFillSmallNotBlack(otbFileName);
                var qFileName    = ExecuteQuantize(startFileName, maxColor);
                var fotsFileName = ExecuteFlattenedOutlineToSource(qFileName, fsnbFileName);
                ExecuteDetermineIsland(fotsFileName, startFileName);
            }
            else if (mode == "dit")
            {
                var otbFileName   = ExecuteOutlineToBlack(startFileName, outlineThreshold);
                var fsnbFileName  = ExecuteFillSmallNotBlack(otbFileName);
                var qFileName     = ExecuteQuantize(startFileName, maxColor);
                var fotsFileName  = ExecuteFlattenedOutlineToSource(qFileName, fsnbFileName);
                var bytesFileName = ExecuteDetermineIsland(fotsFileName, startFileName);
                ExecuteDetermineIslandTest(fsnbFileName, bytesFileName);
                var bbFileName = ExecuteBoxBlur(fsnbFileName, 1);
                ExecuteSdf(bbFileName);
            }
            else
            {
                Console.Out.WriteLine($"Unknown mode provided: {mode}");
                return;
            }
            Console.Out.WriteLine("Completed.");
        }
Пример #19
0
        //
        // TODO: code below needs to be cleaned up & simplified
        //


        public void OnEdit(IChiselHandles handles)
        {
            var newDefinition = this;

            {
                var stepDepthOffset = this.StepDepthOffset;
                var stepHeight      = this.stepHeight;
                var stepCount       = this.StepCount;
                var bounds          = this.bounds;

                var steps = handles.moveSnappingSteps;
                steps.y = stepHeight;

                if (handles.DoBoundsHandle(ref bounds, snappingSteps: steps))
                {
                    newDefinition.bounds = bounds;
                }

                var min = new Vector3(Mathf.Min(bounds.min.x, bounds.max.x), Mathf.Min(bounds.min.y, bounds.max.y), Mathf.Min(bounds.min.z, bounds.max.z));
                var max = new Vector3(Mathf.Max(bounds.min.x, bounds.max.x), Mathf.Max(bounds.min.y, bounds.max.y), Mathf.Max(bounds.min.z, bounds.max.z));

                var size = (max - min);

                var heightStart = bounds.max.y + (bounds.size.y < 0 ? size.y : 0);

                var edgeHeight = heightStart - stepHeight * stepCount;
                var pHeight0   = new Vector3(min.x, edgeHeight, max.z);
                var pHeight1   = new Vector3(max.x, edgeHeight, max.z);

                var depthStart = bounds.min.z - (bounds.size.z < 0 ? size.z : 0);

                var pDepth0 = new Vector3(min.x, max.y, depthStart + stepDepthOffset);
                var pDepth1 = new Vector3(max.x, max.y, depthStart + stepDepthOffset);

                if (handles.DoTurnHandle(ref bounds))
                {
                    newDefinition.bounds = bounds;
                }

                if (handles.DoEdgeHandle1D(out edgeHeight, Axis.Y, pHeight0, pHeight1, snappingStep: stepHeight))
                {
                    var         totalStepHeight = Mathf.Clamp((heightStart - edgeHeight), size.y % stepHeight, size.y);
                    const float kSmudgeValue    = 0.0001f;
                    var         oldStepCount    = newDefinition.StepCount;
                    var         newStepCount    = Mathf.Max(1, Mathf.FloorToInt((Mathf.Abs(totalStepHeight) + kSmudgeValue) / stepHeight));

                    newDefinition.stepDepth     = (oldStepCount * newDefinition.stepDepth) / newStepCount;
                    newDefinition.plateauHeight = size.y - (stepHeight * newStepCount);
                }

                if (handles.DoEdgeHandle1D(out stepDepthOffset, Axis.Z, pDepth0, pDepth1, snappingStep: ChiselLinearStairsDefinition.kMinStepDepth))
                {
                    stepDepthOffset        -= depthStart;
                    stepDepthOffset         = Mathf.Clamp(stepDepthOffset, 0, this.absDepth - ChiselLinearStairsDefinition.kMinStepDepth);
                    newDefinition.stepDepth = ((this.absDepth - stepDepthOffset) / this.StepCount);
                }

                float heightOffset;
                var   prevModified = handles.modified;
                {
                    var direction = Vector3.Cross(Vector3.forward, pHeight0 - pDepth0).normalized;
                    handles.DoEdgeHandle1DOffset(out var height0vec, Axis.Y, pHeight0, pDepth0, direction, snappingStep: stepHeight);
                    handles.DoEdgeHandle1DOffset(out var height1vec, Axis.Y, pHeight1, pDepth1, direction, snappingStep: stepHeight);
                    var height0 = Vector3.Dot(direction, height0vec);
                    var height1 = Vector3.Dot(direction, height1vec);
                    if (Mathf.Abs(height0) > Mathf.Abs(height1))
                    {
                        heightOffset = height0;
                    }
                    else
                    {
                        heightOffset = height1;
                    }
                }
                if (prevModified != handles.modified)
                {
                    newDefinition.plateauHeight += heightOffset;
                }
            }
            if (handles.modified)
            {
                this = newDefinition;
            }
        }
Пример #20
0
        static                         Vector3[] vertices = null; // TODO: store this per instance? or just allocate every frame?

        public void OnEdit(IChiselHandles handles)
        {
            var baseColor = handles.color;
            var normal    = Vector3.up;

            if (BrushMeshFactory.GenerateCapsuleVertices(ref this, ref vertices))
            {
                handles.color = handles.GetStateColor(baseColor, false, false);
                DrawOutline(handles, this, vertices, lineMode: LineMode.ZTest);

                handles.color = handles.GetStateColor(baseColor, false, true);
                DrawOutline(handles, this, vertices, lineMode: LineMode.NoZTest);

                handles.color = baseColor;
            }

            var topPoint    = normal * (this.offsetY + this.height);
            var bottomPoint = normal * (this.offsetY);
            var middlePoint = normal * (this.offsetY + (this.height * 0.5f));
            var radius2D    = new Vector2(this.diameterX, this.diameterZ) * 0.5f;

            var topHeight    = this.topHeight;
            var bottomHeight = this.bottomHeight;

            var maxTopHeight    = this.height - bottomHeight;
            var maxBottomHeight = this.height - topHeight;

            if (this.height < 0)
            {
                normal = -normal;
            }

            var prevModified = handles.modified;

            {
                handles.color = baseColor;
                // TODO: make it possible to (optionally) size differently in x & z
                var radius2Dx = radius2D.x;
                handles.DoRadiusHandle(ref radius2Dx, normal, middlePoint);
                radius2D.x = radius2Dx;

                {
                    var isTopBackfaced  = handles.IsSufaceBackFaced(topPoint, normal);
                    var topLoopHasFocus = false;
                    handles.backfaced = isTopBackfaced;
                    for (int j = this.sides - 1, i = 0; i < this.sides; j = i, i++)
                    {
                        var from = vertices[j + this.topVertexOffset];
                        var to   = vertices[i + this.topVertexOffset];

                        if (handles.DoEdgeHandle1DOffset(out var edgeOffset, UnitySceneExtensions.Axis.Y, from, to, renderLine: false))
                        {
                            topHeight = Mathf.Clamp(topHeight - edgeOffset, 0, maxTopHeight);
                        }
                        topLoopHasFocus = topLoopHasFocus || handles.lastHandleHadFocus;
                    }


                    handles.color = baseColor;
                    handles.DoDirectionHandle(ref topPoint, normal);
                    var topHasFocus = handles.lastHandleHadFocus;
                    handles.backfaced = false;

                    topLoopHasFocus = topLoopHasFocus || (topHasFocus && !this.haveRoundedTop);

                    var thickness = topLoopHasFocus ? kCapLineThicknessSelected : kCapLineThickness;

                    handles.color = handles.GetStateColor(baseColor, topLoopHasFocus, true);
                    handles.DrawLineLoop(vertices, this.topVertexOffset, this.sides, lineMode: LineMode.NoZTest, thickness: thickness);

                    handles.color = handles.GetStateColor(baseColor, topLoopHasFocus, false);
                    handles.DrawLineLoop(vertices, this.topVertexOffset, this.sides, lineMode: LineMode.ZTest, thickness: thickness);
                }

                {
                    var isBottomBackfaced  = handles.IsSufaceBackFaced(bottomPoint, -normal);
                    var bottomLoopHasFocus = false;
                    handles.backfaced = isBottomBackfaced;
                    for (int j = this.sides - 1, i = 0; i < this.sides; j = i, i++)
                    {
                        var from = vertices[j + this.bottomVertexOffset];
                        var to   = vertices[i + this.bottomVertexOffset];

                        if (handles.DoEdgeHandle1DOffset(out var edgeOffset, UnitySceneExtensions.Axis.Y, from, to, renderLine: false))
                        {
                            bottomHeight = Mathf.Clamp(bottomHeight + edgeOffset, 0, maxBottomHeight);
                        }
                        bottomLoopHasFocus = bottomLoopHasFocus || handles.lastHandleHadFocus;
                    }

                    handles.color = baseColor;
                    handles.DoDirectionHandle(ref bottomPoint, -normal);
                    var bottomHasFocus = handles.lastHandleHadFocus;
                    handles.backfaced = false;

                    bottomLoopHasFocus = bottomLoopHasFocus || (bottomHasFocus && !this.haveRoundedBottom);

                    var thickness = bottomLoopHasFocus ? kCapLineThicknessSelected : kCapLineThickness;

                    handles.color = handles.GetStateColor(baseColor, bottomLoopHasFocus, true);
                    handles.DrawLineLoop(vertices, this.bottomVertexOffset, this.sides, lineMode: LineMode.NoZTest, thickness: thickness);

                    handles.color = handles.GetStateColor(baseColor, bottomLoopHasFocus, false);
                    handles.DrawLineLoop(vertices, this.bottomVertexOffset, this.sides, lineMode: LineMode.ZTest, thickness: thickness);
                }
            }
            if (prevModified != handles.modified)
            {
                this.diameterX    = radius2D.x * 2.0f;
                this.height       = topPoint.y - bottomPoint.y;
                this.diameterZ    = radius2D.x * 2.0f;
                this.offsetY      = bottomPoint.y;
                this.topHeight    = topHeight;
                this.bottomHeight = bottomHeight;
                // TODO: handle sizing down (needs to modify transformation?)
            }
        }