// // Copyright (c) 2008-2015 the Urho3D project. // Copyright (c) 2015 Xamarin Inc // Copyright (c) 2016 THUNDERBEAST GAMES LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // using System; using AtomicEngine; namespace FeatureExamples { public class Constraints2DSample : Sample { bool drawDebug; Camera camera; RigidBody2D dummyBody; Node pickedNode; public Constraints2DSample() : base() { } public override void Start() { base.Start(); CreateScene(); SimpleCreateInstructionsWithWasd(", Use PageUp PageDown to zoom.\n Space to toggle debug geometry and joints - F5 to save the scene."); SetupViewport(); SubscribeToEvents(); } void SubscribeToEvents() { SubscribeToEvent(e => { // If draw debug mode is enabled, draw viewport debug geometry, which will show eg. drawable bounding boxes and skeleton // bones. Note that debug geometry has to be separately requested each frame. Disable depth test so that we can see the // bones properly if (drawDebug) scene.GetComponent().DrawDebugGeometry(); }); SubscribeToEvent(HandleMouseButtonDown); if (TouchEnabled) { SubscribeToEvent(HandleTouchBegin3); } } protected override void Update(float timeStep) { SimpleMoveCamera2D(timeStep); var input = GetSubsystem(); if (input.GetKeyDown(Constants.KEY_PAGEUP)) camera.Zoom = (camera.Zoom * 1.01f); if (input.GetKeyDown(Constants.KEY_PAGEDOWN)) camera.Zoom = (camera.Zoom * 0.99f); // Toggle physics debug geometry with space if (input.GetKeyPress(Constants.KEY_SPACE)) drawDebug = !drawDebug; // Save scene if (input.GetKeyPress(Constants.KEY_F5)) { // scene.SaveXml(FileSystem.ProgramDir + "Data/Scenes/Constraints.xml", "\t"); } } void HandleTouchBegin3(TouchBeginEvent args) { var graphics = GetSubsystem(); PhysicsWorld2D physicsWorld = scene.GetComponent(); RigidBody2D rigidBody = physicsWorld.GetRigidBody(new Vector2(args.X, args.Y), uint.MaxValue); // Raycast for RigidBody2Ds to pick if (rigidBody != null) { pickedNode = rigidBody.Node; StaticSprite2D staticSprite = pickedNode.GetComponent(); staticSprite.Color = (new Color(1.0f, 0.0f, 0.0f, 1.0f)); // Temporary modify color of the picked sprite rigidBody = pickedNode.GetComponent(); // Create a ConstraintMouse2D - Temporary apply this constraint to the pickedNode to allow grasping and moving with touch ConstraintMouse2D constraintMouse = pickedNode.CreateComponent(); Vector3 pos = camera.ScreenToWorldPoint(new Vector3((float)args.X / graphics.Width, (float)args.Y / graphics.Height, 0.0f)); constraintMouse.Target = new Vector2(pos.X, pos.Y); constraintMouse.MaxForce = 1000 * rigidBody.Mass; constraintMouse.CollideConnected = true; constraintMouse.OtherBody = dummyBody; // Use dummy body instead of rigidBody. It's better to create a dummy body automatically in ConstraintMouse2D constraintMouse.DampingRatio = 0; } SubscribeToEvent(HandleTouchMove3); SubscribeToEvent(HandleTouchEnd3); } void HandleTouchEnd3(TouchEndEvent args) { if (pickedNode != null) { StaticSprite2D staticSprite = pickedNode.GetComponent(); staticSprite.Color = (new Color(1.0f, 1.0f, 1.0f, 1.0f)); // Restore picked sprite color pickedNode.RemoveComponent(); // Remove temporary constraint pickedNode = null; } UnsubscribeFromEvent(); UnsubscribeFromEvent(); } void HandleTouchMove3(TouchMoveEvent args) { if (pickedNode != null) { var graphics = GetSubsystem(); ConstraintMouse2D constraintMouse = pickedNode.GetComponent(); Vector3 pos = camera.ScreenToWorldPoint(new Vector3((float)args.X / graphics.Width, (float)args.Y / graphics.Height, 0.0f)); constraintMouse.Target = new Vector2(pos.X, pos.Y); } } void HandleMouseButtonDown(MouseButtonDownEvent args) { Input input = GetSubsystem(); PhysicsWorld2D physicsWorld = scene.GetComponent(); RigidBody2D rigidBody = physicsWorld.GetRigidBody(input.MousePosition.X, input.MousePosition.Y, uint.MaxValue); // Raycast for RigidBody2Ds to pick if (rigidBody != null) { pickedNode = rigidBody.Node; //log.Info(pickedNode.name); StaticSprite2D staticSprite = pickedNode.GetComponent(); staticSprite.Color = (new Color(1.0f, 0.0f, 0.0f, 1.0f)); // Temporary modify color of the picked sprite // Create a ConstraintMouse2D - Temporary apply this constraint to the pickedNode to allow grasping and moving with the mouse ConstraintMouse2D constraintMouse = pickedNode.CreateComponent(); constraintMouse.Target = GetMousePositionXY(); constraintMouse.MaxForce = 1000 * rigidBody.Mass; constraintMouse.CollideConnected = true; constraintMouse.OtherBody = dummyBody; // Use dummy body instead of rigidBody. It's better to create a dummy body automatically in ConstraintMouse2D constraintMouse.DampingRatio = 0.0f; } SubscribeToEvent(HandleMouseMove); SubscribeToEvent(HandleMouseButtonUp); } Vector2 GetMousePositionXY() { Input input = GetSubsystem(); var graphics = GetSubsystem(); Vector3 screenPoint = new Vector3((float)input.MousePosition.X / graphics.Width, (float)input.MousePosition.Y / graphics.Height, 0.0f); Vector3 worldPoint = camera.ScreenToWorldPoint(screenPoint); return new Vector2(worldPoint.X, worldPoint.Y); } void HandleMouseMove(MouseMoveEvent args) { if (pickedNode != null) { ConstraintMouse2D constraintMouse = pickedNode.GetComponent(); constraintMouse.Target = GetMousePositionXY(); } } void HandleMouseButtonUp(MouseButtonUpEvent args) { if (pickedNode != null) { StaticSprite2D staticSprite = pickedNode.GetComponent(); staticSprite.Color = (new Color(1.0f, 1.0f, 1.0f, 1.0f)); // Restore picked sprite color pickedNode.RemoveComponent(); pickedNode = null; } UnsubscribeFromEvent(); UnsubscribeFromEvent(); } void SetupViewport() { var renderer = GetSubsystem(); renderer.SetViewport(0, new Viewport(scene, CameraNode.GetComponent())); } void CreateScene() { scene = new Scene(); scene.CreateComponent(); scene.CreateComponent(); PhysicsWorld2D physicsWorld = scene.CreateComponent(); // Create 2D physics world component physicsWorld.DrawJoint = true; // Display the joints (Note that DrawDebugGeometry() must be set to true to acually draw the joints) drawDebug = true; // Set DrawDebugGeometry() to true // Create camera CameraNode = scene.CreateChild("Camera"); // Set camera's position CameraNode.Position = (new Vector3(0.0f, 0.0f, 0.0f)); // Note that Z setting is discarded; use camera.zoom instead (see MoveCamera() below for example) camera = CameraNode.CreateComponent(); camera.Orthographic = true; var graphics = GetSubsystem(); camera.OrthoSize = (float)graphics.Height * PixelSize; camera.Zoom = 1.2f * Math.Min((float)graphics.Width / 1280.0f, (float)graphics.Height / 800.0f); // Set zoom according to user's resolution to ensure full visibility (initial zoom (1.2) is set for full visibility at 1280x800 resolution) // Set up a viewport to the Renderer subsystem so that the 3D scene can be seen Viewport viewport = new Viewport(scene, camera); Renderer renderer = GetSubsystem(); renderer.SetViewport(0, viewport); Zone zone = renderer.DefaultZone; zone.FogColor = (new Color(0.1f, 0.1f, 0.1f)); // Set background color for the scene // Create 4x3 grid for (uint i = 0; i < 5; ++i) { Node edgeNode = scene.CreateChild("VerticalEdge"); RigidBody2D edgeBody = edgeNode.CreateComponent(); if (dummyBody == null) dummyBody = edgeBody; // Mark first edge as dummy body (used by mouse pick) CollisionEdge2D edgeShape = edgeNode.CreateComponent(); edgeShape.SetVertices(new Vector2(i * 2.5f - 5.0f, -3.0f), new Vector2(i * 2.5f - 5.0f, 3.0f)); edgeShape.Friction = 0.5f; // Set friction } for (uint j = 0; j < 4; ++j) { Node edgeNode = scene.CreateChild("HorizontalEdge"); /*RigidBody2D edgeBody = */ edgeNode.CreateComponent(); CollisionEdge2D edgeShape = edgeNode.CreateComponent(); edgeShape.SetVertices(new Vector2(-5.0f, j * 2.0f - 3.0f), new Vector2(5.0f, j * 2.0f - 3.0f)); edgeShape.Friction = 0.5f; // Set friction } var cache = GetSubsystem(); // Create a box (will be cloned later) Node box = scene.CreateChild("Box"); box.Position = (new Vector3(0.8f, -2.0f, 0.0f)); StaticSprite2D boxSprite = box.CreateComponent(); boxSprite.Sprite = cache.Get("Urho2D/Box.png"); RigidBody2D boxBody = box.CreateComponent(); boxBody.BodyType = BodyType2D.BT_DYNAMIC; boxBody.LinearDamping = 0.0f; boxBody.AngularDamping = 0.0f; CollisionBox2D shape = box.CreateComponent(); // Create box shape shape.Size = new Vector2(0.32f, 0.32f); // Set size shape.Density = 1.0f; // Set shape density (kilograms per meter squared) shape.Friction = 0.5f; // Set friction shape.Restitution = 0.1f; // Set restitution (slight bounce) // Create a ball (will be cloned later) Node ball = scene.CreateChild("Ball"); ball.Position = (new Vector3(1.8f, -2.0f, 0.0f)); StaticSprite2D ballSprite = ball.CreateComponent(); ballSprite.Sprite = cache.Get("Urho2D/Ball.png"); RigidBody2D ballBody = ball.CreateComponent(); ballBody.BodyType = BodyType2D.BT_DYNAMIC; ballBody.LinearDamping = 0.0f; ballBody.AngularDamping = 0.0f; CollisionCircle2D ballShape = ball.CreateComponent(); // Create circle shape ballShape.Radius = 0.16f; // Set radius ballShape.Density = 1.0f; // Set shape density (kilograms per meter squared) ballShape.Friction = 0.5f; // Set friction ballShape.Restitution = 0.6f; // Set restitution: make it bounce // Create a polygon Node polygon = scene.CreateChild("Polygon"); polygon.Position = (new Vector3(1.6f, -2.0f, 0.0f)); polygon.SetScale(0.7f); StaticSprite2D polygonSprite = polygon.CreateComponent(); polygonSprite.Sprite = cache.Get("Urho2D/Aster.png"); RigidBody2D polygonBody = polygon.CreateComponent(); polygonBody.BodyType = BodyType2D.BT_DYNAMIC; CollisionPolygon2D polygonShape = polygon.CreateComponent(); polygonShape.VertexCount = 6; // Set number of vertices (mandatory when using SetVertex()) polygonShape.SetVertex(0, new Vector2(-0.8f, -0.3f)); polygonShape.SetVertex(1, new Vector2(0.5f, -0.8f)); polygonShape.SetVertex(2, new Vector2(0.8f, -0.3f)); polygonShape.SetVertex(3, new Vector2(0.8f, 0.5f)); polygonShape.SetVertex(4, new Vector2(0.5f, 0.9f)); polygonShape.SetVertex(5, new Vector2(-0.5f, 0.7f)); polygonShape.Density = 1.0f; // Set shape density (kilograms per meter squared) polygonShape.Friction = 0.3f; // Set friction polygonShape.Restitution = 0.0f; // Set restitution (no bounce) // Create a ConstraintDistance2D CreateFlag("ConstraintDistance2D", -4.97f, 3.0f); // Display Text3D flag Node boxDistanceNode = box.Clone(CreateMode.REPLICATED); Node ballDistanceNode = ball.Clone(CreateMode.REPLICATED); RigidBody2D ballDistanceBody = ballDistanceNode.GetComponent(); boxDistanceNode.Position = (new Vector3(-4.5f, 2.0f, 0.0f)); ballDistanceNode.Position = (new Vector3(-3.0f, 2.0f, 0.0f)); ConstraintDistance2D constraintDistance = boxDistanceNode.CreateComponent(); // Apply ConstraintDistance2D to box constraintDistance.OtherBody = ballDistanceBody; // Constrain ball to box constraintDistance.OwnerBodyAnchor = boxDistanceNode.Position2D; constraintDistance.OtherBodyAnchor = ballDistanceNode.Position2D; // Make the constraint soft (comment to make it rigid, which is its basic behavior) constraintDistance.FrequencyHz = 4.0f; constraintDistance.DampingRatio = 0.5f; // Create a ConstraintFriction2D ********** Not functional. From Box2d samples it seems that 2 anchors are required, Urho2D only provides 1, needs investigation *********** CreateFlag("ConstraintFriction2D", 0.03f, 1.0f); // Display Text3D flag Node boxFrictionNode = box.Clone(CreateMode.REPLICATED); Node ballFrictionNode = ball.Clone(CreateMode.REPLICATED); boxFrictionNode.Position = (new Vector3(0.5f, 0.0f, 0.0f)); ballFrictionNode.Position = (new Vector3(1.5f, 0.0f, 0.0f)); ConstraintFriction2D constraintFriction = boxFrictionNode.CreateComponent(); // Apply ConstraintDistance2D to box constraintFriction.OtherBody = ballFrictionNode.GetComponent(); // Constraint ball to box // Create a ConstraintGear2D CreateFlag("ConstraintGear2D", -4.97f, -1.0f); // Display Text3D flag Node baseNode = box.Clone(CreateMode.REPLICATED); RigidBody2D tempBody = baseNode.GetComponent(); // Get body to make it static tempBody.BodyType = BodyType2D.BT_STATIC; baseNode.Position = (new Vector3(-3.7f, -2.5f, 0.0f)); Node ball1Node = ball.Clone(CreateMode.REPLICATED); ball1Node.Position = (new Vector3(-4.5f, -2.0f, 0.0f)); RigidBody2D ball1Body = ball1Node.GetComponent(); Node ball2Node = ball.Clone(CreateMode.REPLICATED); ball2Node.Position = (new Vector3(-3.0f, -2.0f, 0.0f)); RigidBody2D ball2Body = ball2Node.GetComponent(); ConstraintRevolute2D gear1 = baseNode.CreateComponent(); // Apply constraint to baseBox gear1.OtherBody = ball1Body; // Constrain ball1 to baseBox gear1.Anchor = ball1Node.Position2D; ConstraintRevolute2D gear2 = baseNode.CreateComponent(); // Apply constraint to baseBox gear2.OtherBody = ball2Body; // Constrain ball2 to baseBox gear2.Anchor = ball2Node.Position2D; ConstraintGear2D constraintGear = ball1Node.CreateComponent(); // Apply constraint to ball1 constraintGear.OtherBody = ball2Body; // Constrain ball2 to ball1 constraintGear.OwnerConstraint = gear1; constraintGear.OtherConstraint = gear2; constraintGear.Ratio = 1.0f; ball1Body.ApplyAngularImpulse(0.015f, true); // Animate // Create a vehicle from a compound of 2 ConstraintWheel2Ds CreateFlag("ConstraintWheel2Ds compound", -2.45f, -1.0f); // Display Text3D flag Node car = box.Clone(CreateMode.REPLICATED); car.Scale = new Vector3(4.0f, 1.0f, 0.0f); car.Position = (new Vector3(-1.2f, -2.3f, 0.0f)); StaticSprite2D tempSprite = car.GetComponent(); // Get car Sprite in order to draw it on top tempSprite.OrderInLayer = 0; // Draw car on top of the wheels (set to -1 to draw below) Node ball1WheelNode = ball.Clone(CreateMode.REPLICATED); ball1WheelNode.Position = (new Vector3(-1.6f, -2.5f, 0.0f)); Node ball2WheelNode = ball.Clone(CreateMode.REPLICATED); ball2WheelNode.Position = (new Vector3(-0.8f, -2.5f, 0.0f)); ConstraintWheel2D wheel1 = car.CreateComponent(); wheel1.OtherBody = ball1WheelNode.GetComponent(); wheel1.Anchor = ball1WheelNode.Position2D; wheel1.Axis = new Vector2(0.0f, 1.0f); wheel1.MaxMotorTorque = 20.0f; wheel1.FrequencyHz = 4.0f; wheel1.DampingRatio = 0.4f; ConstraintWheel2D wheel2 = car.CreateComponent(); wheel2.OtherBody = ball2WheelNode.GetComponent(); wheel2.Anchor = ball2WheelNode.Position2D; wheel2.Axis = new Vector2(0.0f, 1.0f); wheel2.MaxMotorTorque = 10.0f; wheel2.FrequencyHz = 4.0f; wheel2.DampingRatio = 0.4f; // ConstraintMotor2D CreateFlag("ConstraintMotor2D", 2.53f, -1.0f); // Display Text3D flag Node boxMotorNode = box.Clone(CreateMode.REPLICATED); tempBody = boxMotorNode.GetComponent(); // Get body to make it static tempBody.BodyType = BodyType2D.BT_STATIC; Node ballMotorNode = ball.Clone(CreateMode.REPLICATED); boxMotorNode.Position = (new Vector3(3.8f, -2.1f, 0.0f)); ballMotorNode.Position = (new Vector3(3.8f, -1.5f, 0.0f)); ConstraintMotor2D constraintMotor = boxMotorNode.CreateComponent(); constraintMotor.OtherBody = ballMotorNode.GetComponent(); // Constrain ball to box constraintMotor.LinearOffset = new Vector2(0.0f, 0.8f); // Set ballNode position relative to boxNode position = (0,0) constraintMotor.AngularOffset = 0.1f; constraintMotor.MaxForce = 5.0f; constraintMotor.MaxTorque = 10.0f; constraintMotor.CorrectionFactor = 1.0f; constraintMotor.CollideConnected = true; // doesn't work // ConstraintMouse2D is demonstrated in HandleMouseButtonDown() function. It is used to "grasp" the sprites with the mouse. CreateFlag("ConstraintMouse2D", 0.03f, -1.0f); // Display Text3D flag // Create a ConstraintPrismatic2D CreateFlag("ConstraintPrismatic2D", 2.53f, 3.0f); // Display Text3D flag Node boxPrismaticNode = box.Clone(CreateMode.REPLICATED); tempBody = boxPrismaticNode.GetComponent(); // Get body to make it static tempBody.BodyType = BodyType2D.BT_STATIC; Node ballPrismaticNode = ball.Clone(CreateMode.REPLICATED); boxPrismaticNode.Position = new Vector3(3.3f, 2.5f, 0.0f); ballPrismaticNode.Position = new Vector3(4.3f, 2.0f, 0.0f); ConstraintPrismatic2D constraintPrismatic = boxPrismaticNode.CreateComponent(); constraintPrismatic.OtherBody = ballPrismaticNode.GetComponent(); // Constrain ball to box constraintPrismatic.Axis = new Vector2(1.0f, 1.0f); // Slide from [0,0] to [1,1] constraintPrismatic.Anchor = new Vector2(4.0f, 2.0f); constraintPrismatic.LowerTranslation = -1.0f; constraintPrismatic.UpperTranslation = 0.5f; constraintPrismatic.EnableLimit = true; constraintPrismatic.MaxMotorForce = 1.0f; constraintPrismatic.MotorSpeed = 0.0f; // ConstraintPulley2D CreateFlag("ConstraintPulley2D", 0.03f, 3.0f); // Display Text3D flag Node boxPulleyNode = box.Clone(CreateMode.REPLICATED); Node ballPulleyNode = ball.Clone(CreateMode.REPLICATED); boxPulleyNode.Position = (new Vector3(0.5f, 2.0f, 0.0f)); ballPulleyNode.Position = (new Vector3(2.0f, 2.0f, 0.0f)); ConstraintPulley2D constraintPulley = boxPulleyNode.CreateComponent(); // Apply constraint to box constraintPulley.OtherBody = ballPulleyNode.GetComponent(); // Constrain ball to box constraintPulley.OwnerBodyAnchor = boxPulleyNode.Position2D; constraintPulley.OtherBodyAnchor = ballPulleyNode.Position2D; constraintPulley.OwnerBodyGroundAnchor = boxPulleyNode.Position2D + new Vector2(0.0f, 1.0f); constraintPulley.OtherBodyGroundAnchor = ballPulleyNode.Position2D + new Vector2(0.0f, 1.0f); constraintPulley.Ratio = 1.0f; // Weight ratio between ownerBody and otherBody // Create a ConstraintRevolute2D CreateFlag("ConstraintRevolute2D", -2.45f, 3.0f); // Display Text3D flag Node boxRevoluteNode = box.Clone(CreateMode.REPLICATED); tempBody = boxRevoluteNode.GetComponent(); // Get body to make it static tempBody.BodyType = BodyType2D.BT_STATIC; Node ballRevoluteNode = ball.Clone(CreateMode.REPLICATED); boxRevoluteNode.Position = (new Vector3(-2.0f, 1.5f, 0.0f)); ballRevoluteNode.Position = (new Vector3(-1.0f, 2.0f, 0.0f)); ConstraintRevolute2D constraintRevolute = boxRevoluteNode.CreateComponent(); // Apply constraint to box constraintRevolute.OtherBody = ballRevoluteNode.GetComponent(); // Constrain ball to box constraintRevolute.Anchor = new Vector2(-1.0f, 1.5f); constraintRevolute.LowerAngle = -1.0f; // In radians constraintRevolute.UpperAngle = 0.5f; // In radians constraintRevolute.EnableLimit = true; constraintRevolute.MaxMotorTorque = 10.0f; constraintRevolute.MotorSpeed = 0.0f; constraintRevolute.EnableMotor = true; // Create a ConstraintRope2D CreateFlag("ConstraintRope2D", -4.97f, 1.0f); // Display Text3D flag Node boxRopeNode = box.Clone(CreateMode.REPLICATED); tempBody = boxRopeNode.GetComponent(); tempBody.BodyType = BodyType2D.BT_STATIC; Node ballRopeNode = ball.Clone(CreateMode.REPLICATED); boxRopeNode.Position = (new Vector3(-3.7f, 0.7f, 0.0f)); ballRopeNode.Position = (new Vector3(-4.5f, 0.0f, 0.0f)); ConstraintRope2D constraintRope = boxRopeNode.CreateComponent(); constraintRope.OtherBody = ballRopeNode.GetComponent(); // Constrain ball to box constraintRope.OwnerBodyAnchor = new Vector2(0.0f, -0.5f); // Offset from box (OwnerBody) : the rope is rigid from OwnerBody center to this ownerBodyAnchor constraintRope.MaxLength = 0.9f; // Rope length constraintRope.CollideConnected = true; // Create a ConstraintWeld2D CreateFlag("ConstraintWeld2D", -2.45f, 1.0f); // Display Text3D flag Node boxWeldNode = box.Clone(CreateMode.REPLICATED); Node ballWeldNode = ball.Clone(CreateMode.REPLICATED); boxWeldNode.Position = (new Vector3(-0.5f, 0.0f, 0.0f)); ballWeldNode.Position = (new Vector3(-2.0f, 0.0f, 0.0f)); ConstraintWeld2D constraintWeld = boxWeldNode.CreateComponent(); constraintWeld.OtherBody = ballWeldNode.GetComponent(); // Constrain ball to box constraintWeld.Anchor = boxWeldNode.Position2D; constraintWeld.FrequencyHz = 4.0f; constraintWeld.DampingRatio = 0.5f; // Create a ConstraintWheel2D CreateFlag("ConstraintWheel2D", 2.53f, 1.0f); // Display Text3D flag Node boxWheelNode = box.Clone(CreateMode.REPLICATED); Node ballWheelNode = ball.Clone(CreateMode.REPLICATED); boxWheelNode.Position = (new Vector3(3.8f, 0.0f, 0.0f)); ballWheelNode.Position = (new Vector3(3.8f, 0.9f, 0.0f)); ConstraintWheel2D constraintWheel = boxWheelNode.CreateComponent(); constraintWheel.OtherBody = ballWheelNode.GetComponent(); // Constrain ball to box constraintWheel.Anchor = ballWheelNode.Position2D; constraintWheel.Axis = new Vector2(0.0f, 1.0f); constraintWheel.EnableMotor = true; constraintWheel.MaxMotorTorque = 1.0f; constraintWheel.MotorSpeed = 0.0f; constraintWheel.FrequencyHz = 4.0f; constraintWheel.DampingRatio = 0.5f; constraintWheel.CollideConnected = true; // doesn't work } void CreateFlag(string text, float x, float y) // Used to create Tex3D flags { /* Node flagNode = scene.CreateChild("Flag"); flagNode.Position = (new Vector3(x, y, 0.0f)); Text3D flag3D = flagNode.CreateComponent(); // We use Text3D in order to make the text affected by zoom (so that it sticks to 2D) flag3D.Text = text; var cache = ResourceCache; flag3D.SetFont(cache.GetFont("Fonts/Anonymous Pro.ttf"), 15); */ } } }