Пример #1
0
        private List <ShapeInfo> PickShapes(RigidBody body, Vector2 worldCoord, Vector2 worldSize)
        {
            Rect worldRect = new Rect(worldCoord.X, worldCoord.Y, worldSize.X, worldSize.Y);

            // Do a physical picking operation
            List <ShapeInfo> result = new List <ShapeInfo>();

            body.PickShapes(worldCoord, worldSize, result);

            // Special case for non-solid / "outline only" shapes, because they are by definition unpickable
            foreach (ShapeInfo shape in body.Shapes)
            {
                VertexBasedShapeInfo vertexShape = shape as VertexBasedShapeInfo;
                if (vertexShape == null)
                {
                    continue;
                }
                if ((vertexShape.ShapeTraits & VertexShapeTrait.IsSolid) != VertexShapeTrait.None)
                {
                    continue;
                }

                Vector2[] vertices = vertexShape.Vertices;
                if (vertices != null && IsOutlineBoxIntersection(body.GameObj.Transform, vertices, worldRect))
                {
                    result.Add(shape);
                    continue;
                }
            }

            return(result);
        }
Пример #2
0
        private ShapeInfo PickShape(RigidBody body, Vector2 worldCoord)
        {
            // Special case for non-solid / "outline only" shapes, because they are by definition unpickable
            Rect worldRect = Rect.Align(Alignment.Center, worldCoord.X, worldCoord.Y, 10.0f, 10.0f);

            foreach (ShapeInfo shape in body.Shapes)
            {
                VertexBasedShapeInfo vertexShape = shape as VertexBasedShapeInfo;
                if (vertexShape == null)
                {
                    continue;
                }
                if ((vertexShape.ShapeTraits & VertexShapeTrait.IsSolid) != VertexShapeTrait.None)
                {
                    continue;
                }

                Vector2[] vertices = vertexShape.Vertices;
                if (vertices != null && IsOutlineBoxIntersection(body.GameObj.Transform, vertices, worldRect))
                {
                    return(shape);
                }
            }

            // Do a physical picking operation
            return(body.PickShape(worldCoord));
        }
Пример #3
0
 public EditRigidBodyPolyShapeAction(VertexBasedShapeInfo shape, Vector2[] originalVertices, Vector2[] newVertices)
 {
     this.targetShape      = shape;
     this.originalVertices = (Vector2[])originalVertices.Clone();
     this.newVertices      = (Vector2[])newVertices.Clone();
     this.sameVertices     = this.AreVerticesEqual(this.originalVertices, this.newVertices);
 }
Пример #4
0
        public override void UpdateAction()
        {
            Vector2 worldPos = this.Environment.ActiveWorldPos.Xy;
            Vector2 localPos = this.Environment.ActiveBodyPos;

            // For circles, emit regular EditProperty UndoRedo actions that adjust
            // Position and Radius properties.
            if (this.activeShape is CircleShapeInfo)
            {
                CircleShapeInfo circle          = this.activeShape as CircleShapeInfo;
                bool            isEditingRadius = this.activeEdge != -1;
                if (isEditingRadius)
                {
                    float oldLocalRadius = circle.Radius;
                    float newLocalRadius = (localPos - circle.Position).Length;
                    if (oldLocalRadius != newLocalRadius)
                    {
                        this.activeEdgeWorldPos = worldPos;
                        UndoRedoManager.Do(new EditPropertyAction(
                                               null,
                                               ReflectionInfo.Property_CircleShapeInfo_Radius,
                                               new object[] { this.activeShape },
                                               new object[] { newLocalRadius }));
                    }
                }
                else
                {
                    Vector2 oldLocalPos = circle.Position;
                    Vector2 newLocalPos = localPos;
                    if (oldLocalPos != newLocalPos)
                    {
                        this.activeEdgeWorldPos = worldPos;
                        UndoRedoManager.Do(new EditPropertyAction(
                                               null,
                                               ReflectionInfo.Property_CircleShapeInfo_Position,
                                               new object[] { this.activeShape },
                                               new object[] { newLocalPos }));
                    }
                }
                circle.UpdateShape();
            }
            // For polygons, just edit the vertices directly. We'll emit a custom
            // UndoRedo action when ending the operation.
            else if (this.activeVertex != -1)
            {
                VertexBasedShapeInfo vertexShape         = this.activeShape as VertexBasedShapeInfo;
                Vector2[]            activeShapeVertices = vertexShape.Vertices;
                Vector2 oldLocalPos = activeShapeVertices[this.activeVertex];
                if (oldLocalPos != localPos)
                {
                    this.activeEdgeWorldPos = worldPos;
                    activeShapeVertices[this.activeVertex] = localPos;
                    vertexShape.Vertices = activeShapeVertices;
                }
            }
        }
Пример #5
0
        private void DrawShape(Canvas canvas, Transform transform, VertexBasedShapeInfo shape, ColorRgba fillColor, ColorRgba outlineColor)
        {
            bool isSolid = (shape.ShapeTraits & VertexShapeTrait.IsSolid) != VertexShapeTrait.None;
            bool isLoop  = (shape.ShapeTraits & VertexShapeTrait.IsLoop) != VertexShapeTrait.None;

            if (isSolid)
            {
                this.FillPolygon(canvas, transform, shape.Vertices, fillColor);
            }

            this.DrawPolygonOutline(canvas, transform, shape.Vertices, outlineColor, isLoop);
        }
Пример #6
0
        public override void BeginAction(MouseButtons mouseButton)
        {
            base.BeginAction(mouseButton);

            // Deselect any potentially selected shapes so the overlay rendering
            // is less cluttered with shape transform areas.
            this.Environment.SelectShapes(null);
            this.isMovingVertex = true;

            if (this.activeShape is CircleShapeInfo)
            {
                this.backedUpShape    = null;
                this.backedUpVertices = null;
            }
            else
            {
                VertexBasedShapeInfo vertexShape         = this.activeShape as VertexBasedShapeInfo;
                Vector2[]            activeShapeVertices = vertexShape.Vertices;

                // Create a backup of the polygons vertices before our edit operation,
                // so we can go back via Undo later.
                this.backedUpShape    = this.activeShape;
                this.backedUpVertices = (Vector2[])activeShapeVertices.Clone();

                // Create a new vertex when hovering the polygon edge where none exists yet
                if (mouseButton == MouseButtons.Left)
                {
                    if (this.activeEdge != -1)
                    {
                        int            newIndex    = this.activeEdge + 1;
                        List <Vector2> newVertices = activeShapeVertices.ToList();
                        newVertices.Insert(newIndex, this.Environment.ActiveBodyPos);

                        vertexShape.Vertices = newVertices.ToArray();
                        this.activeVertex    = newIndex;
                    }
                }
                // Remove an existing vertex when right-clicking on it
                else if (mouseButton == MouseButtons.Right)
                {
                    if (this.activeVertex != -1 && activeShapeVertices.Length > 3)
                    {
                        List <Vector2> newVertices = activeShapeVertices.ToList();
                        newVertices.RemoveAt(this.activeVertex);

                        vertexShape.Vertices = newVertices.ToArray();
                        this.activeVertex    = -1;
                    }
                }
            }
        }
Пример #7
0
        public override void EndAction()
        {
            this.isMovingVertex = false;

            // Emit a vertex edit UndoRedo action for polygon operations. It will
            // replace the entire vertex array with the backed up version on undo.
            if (this.backedUpShape != null)
            {
                VertexBasedShapeInfo vertexShape = this.backedUpShape as VertexBasedShapeInfo;
                UndoRedoManager.Do(new EditRigidBodyPolyShapeAction(
                                       vertexShape,
                                       this.backedUpVertices,
                                       vertexShape.Vertices));
            }
        }
Пример #8
0
 private void DrawShapeOutline(Canvas canvas, Transform transform, ShapeInfo shape)
 {
     canvas.PushState();
     if (shape is CircleShapeInfo)
     {
         this.DrawShapeOutline(canvas, transform, shape as CircleShapeInfo);
     }
     else if (shape is VertexBasedShapeInfo)
     {
         VertexBasedShapeInfo vertexShape = shape as VertexBasedShapeInfo;
         bool isLoop = (vertexShape.ShapeTraits & VertexShapeTrait.IsLoop) != VertexShapeTrait.None;
         this.DrawShapeOutline(canvas, transform, vertexShape.Vertices, isLoop);
     }
     canvas.PopState();
 }
Пример #9
0
        public override void EndAction()
        {
            base.EndAction();
            if (this.actionShape != null)
            {
                Vector2[] vertices = this.actionShape.Vertices;

                // If we're in the process of placing another vertex, remove that unplaced one
                if (!this.pendingAdvance)
                {
                    List <Vector2> prevVertices = vertices.ToList();
                    prevVertices.RemoveAt(this.currentVertex);
                    vertices = prevVertices.ToArray();
                }

                // Apply the new polygon and force an immediate body update / sync, so
                // we can get a useful result from the shape's IsValid method.
                this.actionShape.Vertices = vertices;
                this.Environment.ActiveBody.SynchronizeBodyShape();

                // Check if we have a fully defined, valid polygon to apply
                bool isValidShape            = this.actionShape != null && this.actionShape.IsValid;
                bool isMinVertexCountReached =
                    vertices.Length >= this.initialVertexCount &&
                    this.currentVertex >= this.initialVertexCount - 1;
                if (isValidShape && isMinVertexCountReached)
                {
                    // Remove the shape and re-add it properly using an UndoRedoAction.
                    // Now that we're sure the shape is valid, we want its creation to
                    // show up in the UndoRedo stack.
                    this.Environment.ActiveBody.RemoveShape(this.actionShape);
                    UndoRedoManager.Do(new CreateRigidBodyShapeAction(this.Environment.ActiveBody, this.actionShape));
                    this.Environment.SelectTool(typeof(VertexRigidBodyEditorTool));
                }
                else
                {
                    this.Environment.SelectShapes(null);
                    this.Environment.ActiveBody.RemoveShape(this.actionShape);
                }

                DualityEditorApp.NotifyObjPropChanged(this,
                                                      new ObjectSelection(this.Environment.ActiveBody),
                                                      ReflectionInfo.Property_RigidBody_Shapes);
                this.actionShape = null;
            }
        }
Пример #10
0
 private void DrawShapeArea(Canvas canvas, Transform transform, ShapeInfo shape)
 {
     canvas.PushState();
     if (shape is CircleShapeInfo)
     {
         this.DrawShapeArea(canvas, transform, shape as CircleShapeInfo);
     }
     else if (shape is PolyShapeInfo)
     {
         PolyShapeInfo polyShape = shape as PolyShapeInfo;
         Rect          bounds    = polyShape.Vertices.BoundingBox();
         Rect          texRect   = new Rect(1.0f, 1.0f);
         if (this.wrapTexture)
         {
             texRect.W = bounds.W / canvas.State.TextureBaseSize.X;
             texRect.H = bounds.H / canvas.State.TextureBaseSize.Y;
         }
         if (polyShape.ConvexPolygons != null)
         {
             foreach (Vector2[] convexPolygon in polyShape.ConvexPolygons)
             {
                 Rect localBounds  = convexPolygon.BoundingBox();
                 Rect localTexRect = new Rect(
                     texRect.X + texRect.W * (localBounds.X - bounds.X) / bounds.W,
                     texRect.Y + texRect.H * (localBounds.Y - bounds.Y) / bounds.H,
                     texRect.W * localBounds.W / bounds.W,
                     texRect.H * localBounds.H / bounds.H);
                 this.DrawShapeArea(canvas, transform, convexPolygon, localTexRect);
             }
         }
     }
     else if (shape is VertexBasedShapeInfo)
     {
         VertexBasedShapeInfo vertexShape = shape as VertexBasedShapeInfo;
         bool isSolid = (vertexShape.ShapeTraits & VertexShapeTrait.IsSolid) != VertexShapeTrait.None;
         if (isSolid || this.fillHollowShapes)
         {
             this.DrawShapeArea(canvas, transform, vertexShape.Vertices);
         }
     }
     canvas.PopState();
 }
Пример #11
0
        public override void BeginAction(MouseButtons mouseButton)
        {
            base.BeginAction(mouseButton);

            Vector2[] initialVertices = this.GetInitialVertices(this.Environment.ActiveBodyPos);

            this.currentVertex      = 1;
            this.actionShape        = this.CreateShapeInfo(initialVertices);
            this.initialVertexCount = initialVertices.Length;

            // Add the shape to the body. We're not doing an actual UndoRedoAction
            // just yet, because we don't want a potentially invalid under-construction
            // polygon to show up in the UndoRedo stack. We'll do a proper UndoRedoAction
            // when finishing up the shape.
            this.Environment.ActiveBody.AddShape(this.actionShape);
            DualityEditorApp.NotifyObjPropChanged(this,
                                                  new ObjectSelection(this.Environment.ActiveBody),
                                                  ReflectionInfo.Property_RigidBody_Shapes);

            this.Environment.SelectShapes(new ShapeInfo[] { this.actionShape });
        }
Пример #12
0
        protected internal override void OnCollectWorldOverlayDrawcalls(Canvas canvas)
        {
            base.OnCollectWorldOverlayDrawcalls(canvas);
            List <RigidBody> visibleColliders = this.QueryVisibleColliders().ToList();

            RigidBody selectedBody = this.QuerySelectedCollider();

            canvas.State.SetMaterial(DrawTechnique.Alpha);
            canvas.State.TextFont    = Font.GenericMonospace10;
            canvas.State.DepthOffset = this.depthOffset;
            Font textFont = canvas.State.TextFont.Res;

            // Retrieve selected shapes
            ObjectEditorCamViewState editorState = this.View.ActiveState as ObjectEditorCamViewState;

            object[] editorSelectedObjects = editorState != null?editorState.SelectedObjects.Select(item => item.ActualObject).ToArray() : new object[0];

            bool isAnyBodySelected  = (selectedBody != null);
            bool isAnyShapeSelected = isAnyBodySelected && editorSelectedObjects.OfType <ShapeInfo>().Any();

            // Draw Shape layer
            foreach (RigidBody body in visibleColliders)
            {
                if (!body.Shapes.Any())
                {
                    continue;
                }

                Vector3 objPos   = body.GameObj.Transform.Pos;
                float   objAngle = body.GameObj.Transform.Angle;
                float   objScale = body.GameObj.Transform.Scale;

                bool isBodySelected = (body == selectedBody);

                float bodyAlpha  = isBodySelected ? 1.0f : (isAnyBodySelected ? 0.5f : 1.0f);
                float maxDensity = body.Shapes.Max(s => s.Density);
                float minDensity = body.Shapes.Min(s => s.Density);
                float avgDensity = (maxDensity + minDensity) * 0.5f;

                int shapeIndex = 0;
                foreach (ShapeInfo shape in body.Shapes)
                {
                    bool isShapeSelected = isBodySelected && editorSelectedObjects.Contains(shape);

                    float     shapeAlpha      = bodyAlpha * (isShapeSelected ? 1.0f : (isAnyShapeSelected && isBodySelected ? 0.75f : 1.0f));
                    float     densityRelative = MathF.Abs(maxDensity - minDensity) < 0.01f ? 1.0f : shape.Density / avgDensity;
                    ColorRgba shapeColor      = shape.IsSensor ? this.ShapeSensorColor : this.ShapeColor;
                    ColorRgba fontColor       = this.FgColor;

                    if (!body.IsAwake)
                    {
                        shapeColor = shapeColor.ToHsva().WithSaturation(0.0f).ToRgba();
                    }
                    if (!shape.IsValid)
                    {
                        shapeColor = this.ShapeErrorColor;
                    }

                    // Draw the shape itself
                    ColorRgba fillColor    = shapeColor.WithAlpha((0.25f + densityRelative * 0.25f) * shapeAlpha);
                    ColorRgba outlineColor = ColorRgba.Lerp(shapeColor, fontColor, isShapeSelected ? 0.75f : 0.25f).WithAlpha(shapeAlpha);
                    this.DrawShape(canvas, body.GameObj.Transform, shape, fillColor, outlineColor);

                    // Calculate the center coordinate
                    Vector2 shapeCenter = Vector2.Zero;
                    if (shape is CircleShapeInfo)
                    {
                        CircleShapeInfo circleShape = shape as CircleShapeInfo;
                        shapeCenter = circleShape.Position * objScale;
                    }
                    else if (shape is VertexBasedShapeInfo)
                    {
                        VertexBasedShapeInfo vertexShape   = shape as VertexBasedShapeInfo;
                        Vector2[]            shapeVertices = vertexShape.Vertices;

                        for (int i = 0; i < shapeVertices.Length; i++)
                        {
                            shapeCenter += shapeVertices[i];
                        }

                        shapeCenter /= shapeVertices.Length;
                    }
                    MathF.TransformCoord(ref shapeCenter.X, ref shapeCenter.Y, objAngle, objScale);

                    // Draw shape index
                    if (body == selectedBody)
                    {
                        string  indexText = shapeIndex.ToString();
                        Vector2 textSize  = textFont.MeasureText(indexText);
                        canvas.State.ColorTint      = fontColor.WithAlpha((shapeAlpha + 1.0f) * 0.5f);
                        canvas.State.TransformScale = Vector2.One / canvas.DrawDevice.GetScaleAtZ(0.0f);
                        canvas.DrawText(indexText,
                                        objPos.X + shapeCenter.X,
                                        objPos.Y + shapeCenter.Y,
                                        0.0f);
                        canvas.State.TransformScale = Vector2.One;
                    }

                    shapeIndex++;
                }

                // Draw center of mass
                if (body.BodyType == BodyType.Dynamic)
                {
                    Vector2 localMassCenter = body.LocalMassCenter;
                    MathF.TransformCoord(ref localMassCenter.X, ref localMassCenter.Y, objAngle, objScale);

                    float size = this.GetScreenConstantScale(canvas, 6.0f);

                    canvas.State.ColorTint = this.MassCenterColor.WithAlpha(bodyAlpha);
                    canvas.DrawLine(
                        objPos.X + localMassCenter.X - size,
                        objPos.Y + localMassCenter.Y,
                        0.0f,
                        objPos.X + localMassCenter.X + size,
                        objPos.Y + localMassCenter.Y,
                        0.0f);
                    canvas.DrawLine(
                        objPos.X + localMassCenter.X,
                        objPos.Y + localMassCenter.Y - size,
                        0.0f,
                        objPos.X + localMassCenter.X,
                        objPos.Y + localMassCenter.Y + size,
                        0.0f);
                }

                // Draw transform center
                {
                    float size = this.GetScreenConstantScale(canvas, 3.0f);
                    canvas.State.ColorTint = this.ObjectCenterColor.WithAlpha(bodyAlpha);
                    canvas.FillCircle(objPos.X, objPos.Y, 0.0f, size);
                }
            }
        }
Пример #13
0
 public RigidBodyEditorSelVertexShape(VertexBasedShapeInfo shape) : base(shape)
 {
     this.UpdateShapeStats();
 }
Пример #14
0
        private void UpdateHoverState()
        {
            this.activeShape        = null;
            this.activeVertex       = -1;
            this.activeEdge         = -1;
            this.activeEdgeWorldPos = Vector2.Zero;

            RigidBody body = this.Environment.ActiveBody;

            if (body == null)
            {
                return;
            }

            DesignTimeObjectData designTimeData = DesignTimeObjectData.Get(body.GameObj);

            if (designTimeData.IsHidden)
            {
                return;
            }

            // Prepare the transform matrix for this object, so
            // we can move the RigidBody vertices into world space quickly
            Transform transform = body.GameObj.Transform;
            float     bodyScale = transform.Scale;
            Vector2   bodyPos   = transform.Pos.Xy;
            Vector2   bodyDotX;
            Vector2   bodyDotY;

            MathF.GetTransformDotVec(transform.Angle, bodyScale, out bodyDotX, out bodyDotY);

            Vector3 mousePosWorld = this.Environment.HoveredWorldPos;

            foreach (ShapeInfo shape in body.Shapes)
            {
                // Determine whether the cursor is hovering something it can interact with
                bool  anythingHovered = false;
                float hotRadius       = 8.0f;
                float worldHotRadius  = hotRadius / MathF.Max(0.0001f, this.Environment.GetScaleAtZ(0.0f));
                if (shape is CircleShapeInfo)
                {
                    CircleShapeInfo circle = shape as CircleShapeInfo;

                    // Determine world space position and radius of the circle shape
                    float   circleWorldRadius = circle.Radius * bodyScale;
                    Vector2 circleWorldPos    = circle.Position;
                    MathF.TransformDotVec(ref circleWorldPos, ref bodyDotX, ref bodyDotY);
                    circleWorldPos = bodyPos + circleWorldPos;

                    float hoverDist = (circleWorldPos - mousePosWorld.Xy).Length;

                    // Hovering the center
                    if (hoverDist <= worldHotRadius)
                    {
                        this.activeVertex       = 0;
                        this.activeEdge         = -1;
                        this.activeEdgeWorldPos = circleWorldPos;
                        anythingHovered         = true;
                    }
                    // Hovering the edge
                    else if (MathF.Abs(hoverDist - circleWorldRadius) <= worldHotRadius)
                    {
                        this.activeVertex       = -1;
                        this.activeEdge         = 0;
                        this.activeEdgeWorldPos =
                            circleWorldPos +
                            circleWorldRadius * (mousePosWorld.Xy - circleWorldPos).Normalized;
                        anythingHovered = true;
                    }
                }
                else if (shape is VertexBasedShapeInfo)
                {
                    VertexBasedShapeInfo vertexShape = shape as VertexBasedShapeInfo;
                    Vector2[]            vertices    = vertexShape.Vertices;
                    if (vertices == null)
                    {
                        continue;
                    }

                    Vector2[] worldVertices = new Vector2[vertices.Length];

                    // Transform the shapes vertices into world space
                    for (int index = 0; index < vertices.Length; index++)
                    {
                        Vector2 vertex = vertices[index];
                        MathF.TransformDotVec(ref vertex, ref bodyDotX, ref bodyDotY);
                        worldVertices[index] = bodyPos + vertex;
                    }
                    anythingHovered =
                        this.GetHoveredVertex(worldVertices, mousePosWorld.Xy, worldHotRadius, out this.activeVertex, out this.activeEdgeWorldPos) ||
                        this.GetHoveredEdge(worldVertices, mousePosWorld.Xy, worldHotRadius, out this.activeEdge, out this.activeEdgeWorldPos);
                }

                if (anythingHovered)
                {
                    this.activeShape = shape;
                    break;
                }
            }
        }
Пример #15
0
        public override void OnWorldOverlayDrawcalls(Canvas canvas)
        {
            RigidBody body = this.Environment.ActiveBody;

            if (body == null)
            {
                return;
            }

            DesignTimeObjectData designTimeData = DesignTimeObjectData.Get(body.GameObj);

            if (designTimeData.IsHidden)
            {
                return;
            }

            float knobSize      = 7.0f;
            float worldKnobSize = knobSize / MathF.Max(0.0001f, canvas.DrawDevice.GetScaleAtZ(0.0f));

            // Determine the color in which we'll draw the interaction markers
            ColorRgba markerColor = this.Environment.FgColor;

            canvas.State.DepthOffset = -1.0f;

            // Prepare the transform matrix for this object, so
            // we can move the RigidBody vertices into world space quickly
            Transform transform = body.GameObj.Transform;
            Vector2   bodyPos   = transform.Pos.Xy;
            Vector2   bodyDotX;
            Vector2   bodyDotY;

            MathF.GetTransformDotVec(transform.Angle, transform.Scale, out bodyDotX, out bodyDotY);

            // Draw an interaction indicator for every vertex of the active bodies shapes
            Vector3 mousePosWorld = this.Environment.ActiveWorldPos;

            foreach (ShapeInfo shape in body.Shapes)
            {
                if (shape is CircleShapeInfo)
                {
                    CircleShapeInfo circle = shape as CircleShapeInfo;

                    Vector2 circleWorldPos = circle.Position;
                    MathF.TransformDotVec(ref circleWorldPos, ref bodyDotX, ref bodyDotY);
                    circleWorldPos = bodyPos + circleWorldPos;

                    // Draw the circles center as a vertex
                    if (this.activeVertex == 0 && this.activeShape == shape)
                    {
                        canvas.State.ColorTint = markerColor;
                    }
                    else
                    {
                        canvas.State.ColorTint = markerColor.WithAlpha(0.75f);
                    }

                    canvas.FillRect(
                        circleWorldPos.X - worldKnobSize * 0.5f,
                        circleWorldPos.Y - worldKnobSize * 0.5f,
                        worldKnobSize,
                        worldKnobSize);
                }
                else if (shape is VertexBasedShapeInfo)
                {
                    VertexBasedShapeInfo vertexShape = shape as VertexBasedShapeInfo;
                    Vector2[]            vertices    = vertexShape.Vertices;
                    if (vertices == null)
                    {
                        continue;
                    }

                    Vector2[] worldVertices = new Vector2[vertices.Length];

                    // Transform the shapes vertices into world space
                    for (int index = 0; index < vertices.Length; index++)
                    {
                        Vector2 vertex = vertices[index];
                        MathF.TransformDotVec(ref vertex, ref bodyDotX, ref bodyDotY);
                        worldVertices[index] = bodyPos + vertex;
                    }

                    // Draw the vertices
                    for (int i = 0; i < worldVertices.Length; i++)
                    {
                        if (this.activeVertex == i && this.activeShape == shape)
                        {
                            canvas.State.ColorTint = markerColor;
                        }
                        else
                        {
                            canvas.State.ColorTint = markerColor.WithAlpha(0.75f);
                        }

                        canvas.FillRect(
                            worldVertices[i].X - worldKnobSize * 0.5f,
                            worldVertices[i].Y - worldKnobSize * 0.5f,
                            worldKnobSize,
                            worldKnobSize);
                    }
                }
            }

            // Interaction indicator for an existing vertex
            if (this.activeVertex != -1)
            {
                canvas.State.ColorTint = markerColor;
                canvas.DrawRect(
                    this.activeEdgeWorldPos.X - worldKnobSize,
                    this.activeEdgeWorldPos.Y - worldKnobSize,
                    worldKnobSize * 2.0f,
                    worldKnobSize * 2.0f);
            }
            // Interaction indicator for a vertex-to-be-created
            else if (this.activeEdge != -1)
            {
                canvas.State.ColorTint = markerColor.WithAlpha(0.35f);
                canvas.FillRect(
                    this.activeEdgeWorldPos.X - worldKnobSize * 0.5f,
                    this.activeEdgeWorldPos.Y - worldKnobSize * 0.5f,
                    worldKnobSize * 1.0f,
                    worldKnobSize * 1.0f);
                canvas.State.ColorTint = markerColor;
                canvas.DrawRect(
                    this.activeEdgeWorldPos.X - worldKnobSize,
                    this.activeEdgeWorldPos.Y - worldKnobSize,
                    worldKnobSize * 2.0f,
                    worldKnobSize * 2.0f);
            }
        }