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); }
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)); }
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); }
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; } } }
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); }
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; } } } }
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)); } }
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(); }
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; } }
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(); }
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 }); }
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); } } }
public RigidBodyEditorSelVertexShape(VertexBasedShapeInfo shape) : base(shape) { this.UpdateShapeStats(); }
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; } } }
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); } }