// Blob Object, Brian Richardson @ GG 2008 // Based based on article/code by Mick West (http://cowboyprogramming.com/2007/01/05/blob-physics/) // To add it to your project, add these XML blocks to your level.xml file. // // // data/images/face.png // true // // // data/images/ears.png // true // // // Note: This assumes that you're adding the BlobComponent to StartGame2D, if you've changed // the namespace, update this as well. // // // // // false // false // // // 0 // // // false // 0 // // 10.000 // 10.000 // // // 0.000 // 0.000 // // 0 // true // true // 1 // false // false // // 0.000000 // 0.000000 // // // // // Notes: // This was a quick hack, it doesn't respect TorqueX's collision system. To do this, you'd create a new CollisionConstraint that // would query Torque's collision info. // // You can only add forces to all particles in a VerletSystem. To do something more useful, it'd be smart to have start/end // indices for each blob, then you could apply forces to single blobs. // // This was a quick hack, ;) I'm sure there are other bad things going on in here. using System; using System.Collections.Generic; using System.Text; using System.Xml.Serialization; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using GarageGames.Torque.Core; using GarageGames.Torque.Util; using GarageGames.Torque.Sim; using GarageGames.Torque.T2D; using GarageGames.Torque.SceneGraph; using GarageGames.Torque.MathUtil; using GarageGames.Torque.GUI; using GarageGames.Torque.GFX; using GarageGames.Torque.Materials; using GarageGames.Torque.RenderManager; using GarageGames.Torque.Platform; namespace StarterGame2D { public interface IVerletConstraint { void satisfy(VerletPoint vp); Vector3 getForce(VerletPoint vp); void debugRender(VerletPoint vp, ref GFXVertexFormat.PCTTBN[] vertices, ref int curIndex); int vertsNeeded(); } public class SemiRigidConstraint : IVerletConstraint { private VerletPoint mOtherVerlet; private float mMin, mMid, mMax, mForce; private bool mRender; public SemiRigidConstraint(VerletPoint p_other_point, float min, float mid, float max, float force, bool render) { mOtherVerlet = p_other_point; mMin = min; mMid = mid; mMax = max; mForce = force; mRender = render; } // // IVerletConstraint // public void satisfy(VerletPoint vp) { Vector3 toMe = vp.position - mOtherVerlet.position; Vector3 mid = (vp.position + mOtherVerlet.position) / 2.0f; if (toMe.Length() < 0.00001f) { toMe = Vector3.Right; } float radius = toMe.Length(); radius = Math.Max(radius, mMin); radius = Math.Min(radius, mMax); toMe.Normalize(); toMe *= radius; vp.position = mid + toMe / 2.0f; mOtherVerlet.position = mid - toMe / 2.0f; } public Vector3 getForce(VerletPoint vp) { Vector3 toMe = vp.position - mOtherVerlet.position; if (toMe.Length() < 0.00001f) { toMe = Vector3.Right; } toMe.Normalize(); Vector3 mid = mOtherVerlet.position + toMe * mMid; Vector3 toMid = mid - vp.position; return toMid * mForce; } public void debugRender(VerletPoint vp, ref GFXVertexFormat.PCTTBN[] vertices, ref int curIndex) { if (!mRender) return; vertices[curIndex] = new GFXVertexFormat.PCTTBN(); vertices[curIndex].Position = new Vector3(vp.position.X, vp.position.Y, vp.position.Z); vertices[curIndex].Color = new Color(0,0,0); curIndex++; vertices[curIndex] = new GFXVertexFormat.PCTTBN(); vertices[curIndex].Position = new Vector3(mOtherVerlet.position.X, mOtherVerlet.position.Y, mOtherVerlet.position.Z); vertices[curIndex].Color = new Color(0, 0, 0); curIndex++; } public int vertsNeeded() { if (mRender) return 2; else return 0; } } public class LineConstraint : IVerletConstraint { public enum ConstraintType { Above, Below, Left, Right } protected float mVal; protected ConstraintType mType; public LineConstraint(float v, ConstraintType ctype) { mVal = v; mType = ctype; } // // IVerletConstraint // virtual public void satisfy(VerletPoint vp) { switch (mType) { case ConstraintType.Above : { if (vp.position.Y > mVal) { vp.position = new Vector3(vp.position.X, mVal, vp.position.Z); } } break; case ConstraintType.Below: { if (vp.position.Y < mVal) { vp.position = new Vector3(vp.position.X, mVal, vp.position.Z); } } break; case ConstraintType.Right: { if (vp.position.X < mVal) { vp.position = new Vector3(mVal, vp.position.Y, vp.position.Z); } } break; case ConstraintType.Left: { if (vp.position.X > mVal) { vp.position = new Vector3(mVal, vp.position.Y, vp.position.Z); } } break; } } public Vector3 getForce(VerletPoint vp) { return Vector3.Zero; } public void debugRender(VerletPoint vp, ref GFXVertexFormat.PCTTBN[] vertices, ref int curIndex) { } public int vertsNeeded() { return 0; } } public class VerletPoint { public VerletPoint(Vector3 pos) { mPos = pos; mLastPos = pos; mMass = 1; mConstraints = new List(); ; mCollisionConstraints = new List(); } public int numConstraints() { return mConstraints.Count; } public int vertsNeeded() { int verts = 0; foreach (IVerletConstraint con in mConstraints) verts += con.vertsNeeded(); return verts; } public void debugRender(ref GFXVertexFormat.PCTTBN[] vertices, ref int curIndex) { foreach (IVerletConstraint con in mConstraints) con.debugRender(this, ref vertices, ref curIndex); } public void addConstraint(IVerletConstraint constraint) { mConstraints.Add(constraint); } public void addCollisionConstraint(IVerletConstraint constraint) { mCollisionConstraints.Add(constraint); } public void gatherForces() { foreach (IVerletConstraint c in mConstraints) mForce += c.getForce(this); } public void integrate(float dt) { Vector3 last = mPos; mPos += (mPos - mLastPos) + mForce / mMass * dt * dt; mLastPos = last; } public void satisfyConstraints() { foreach (IVerletConstraint con in mConstraints) con.satisfy(this); } public void satisfyCollisionConstraints() { foreach (IVerletConstraint con in mCollisionConstraints) con.satisfy(this); } public Vector3 position { get { return mPos; } set { mPos = value; } } public Vector3 LastPosition { get { return mLastPos; } } public Vector3 Force { get { return mForce; } set { mForce = value; } } public float Mass { get { return mMass; } } private Vector3 mForce; private float mMass; private Vector3 mPos; private Vector3 mLastPos; private List mConstraints; private List mCollisionConstraints; private List mCollisionConstraints2; } public class VerletSystem { VerletSystem() { mVerletPoints = new List(); } public VerletPoint createPoint(Vector3 pos) { VerletPoint vp = new VerletPoint(pos); mVerletPoints.Add(vp); return vp; } static public VerletSystem get() { if (mSystem != null) return mSystem; mSystem = new VerletSystem(); return mSystem; } static private VerletSystem mSystem; private List mVerletPoints; public List Points { get { return mVerletPoints; } } public int numVerts() { int ret = 0; foreach (VerletPoint v in Points) { ret += v.vertsNeeded(); } return ret; } public Box3F bounds() { Box3F ret; ret.Min = new Vector3(1000, 1000, 1000); ret.Max = new Vector3(-1000, -1000, -1000); foreach (VerletPoint v in Points) { ret.Min = Vector3.Min(ret.Min, v.position); ret.Max = Vector3.Max(ret.Max, v.position); } return ret; } static public void SemiRigidConstraint(VerletPoint p1, VerletPoint p2, float min, float mid, float max, float force, bool render) { SemiRigidConstraint p_r1 = new SemiRigidConstraint(p2, min, mid, max, force, render); p1.addConstraint(p_r1); SemiRigidConstraint p_r2 = new SemiRigidConstraint(p1, min, mid, max, force, render); p2.addConstraint(p_r2); } public void integrate(Vector3 addForce) { Vector3 gravity = new Vector3(0, 1, 0); // Figure our our forces foreach (VerletPoint v in mVerletPoints) { v.Force = gravity * v.Mass; v.Force += addForce; // Add other forces like jumping, etc.. v.gatherForces(); } foreach (VerletPoint v in mVerletPoints) { v.integrate(0.016666f); } foreach (VerletPoint v in mVerletPoints) { v.satisfyConstraints(); } foreach (VerletPoint v in mVerletPoints) { v.satisfyCollisionConstraints(); } } } [TorqueXmlSchemaType] public class BlobComponent : T2DSceneObject { //====================================================== #region Static methods, fields, constructors #endregion /// /// Specifies the Sprite's Texture /// public RenderMaterial FaceMaterial { // forward access to quad object get { return _facequad.Material; } set { _facequad.Material = value; } } public RenderMaterial EarMaterial { // forward access to quad object get { return _earquad.Material; } set { _earquad.Material = value; } } //====================================================== #region Constructors private RenderMaterial _material; public BlobComponent() { GarageGames.Torque.Materials.SimpleMaterial seffect = new GarageGames.Torque.Materials.SimpleMaterial(); _material = seffect; } #endregion //====================================================== #region Public properties, operators, constants, and enums #endregion //====================================================== #region Public methods private int count = 0; private Vector3 mForce; public override void ProcessTick(Move move, float dt) { base.ProcessTick(move, dt); //if (count++ < 10) // return; count = 0; // Do verlet integration for (int i = 0; i < 3; i++ ) VerletSystem.get().integrate(mForce); mForce = Vector3.Zero; } public void setMoveForce(Vector3 f) { mForce = f; } public override void InterpolateTick(float k) { // todo: interpolate between ticks as needed here } private Resource _vb; private int numVerts; private void _CreateVB() { if (!_vb.IsNull) return; Assert.Fatal(_vb.IsNull, "doh"); numVerts = VerletSystem.get().numVerts(); int sizeInBytes = numVerts * GFXVertexFormat.VertexSize; _vb = ResourceManager.Instance.CreateVertexBuffer(ResourceProfiles.ManualStaticVBProfile, sizeInBytes); } private void _FillVB() { Assert.Fatal(!_vb.IsNull, "doh"); // fill in vertex array int sizeInBytes = numVerts * GFXVertexFormat.VertexSize; GFXVertexFormat.PCTTBN[] vertices = TorqueUtil.GetScratchArray(numVerts); int curIndex = 0; foreach (VerletPoint p in VerletSystem.get().Points) { p.debugRender(ref vertices, ref curIndex); } _vb.Instance.SetData(vertices, 0, numVerts); } public override void Render(SceneRenderState srs) { // Render stuff _CreateVB(); _FillVB(); Box3F b = VerletSystem.get().bounds(); Vector3 scaleVec = (b.Max - b.Min) / 2.0f; Vector3 faceScaleVec = scaleVec; faceScaleVec *= 0.8f; // fudge Matrix scale = Matrix.CreateScale(faceScaleVec); Matrix trans = Matrix.CreateTranslation(b.Center); Matrix objToWorld = scale * trans; _facequad.Render(objToWorld, 1.0f, srs); GraphicsDevice d3d = srs.Gfx.Device; RenderInstance ri = SceneRenderer.RenderManager.AllocateInstance(); ri.Type = RenderInstance.RenderInstanceType.Mesh2D; ri.ObjectTransform = Matrix.Identity; ri.VertexBuffer = _vb.Instance; ri.PrimitiveType = PrimitiveType.LineList; ri.VertexSize = GFXVertexFormat.VertexSize; ri.VertexDeclaration = GFXVertexFormat.GetVertexDeclaration(d3d); ri.VertexCount = numVerts; ri.BaseVertex = 0; ri.PrimitiveCount = numVerts - 1; ri.Opacity = VisibilityLevel; ri.UTextureAddressMode = TextureAddressMode.Clamp; ri.VTextureAddressMode = TextureAddressMode.Clamp; ri.Material = _material; SceneRenderer.RenderManager.AddInstance(ri); Vector3 earScaleVec = scaleVec; earScaleVec.Y = 5.0f; Vector3 eartrans = b.Center; eartrans.Y -= scaleVec.Y * 0.65f; scale = Matrix.CreateScale(earScaleVec); trans = Matrix.CreateTranslation(eartrans); objToWorld = scale * trans; _earquad.Render(objToWorld, 1.0f, srs); base.Render(srs); } public override bool OnRegister() { if (!base.OnRegister()) return false; SetTicking(); createBlob(40, new Vector3(0.0f, 0.0f, 0.0f), 8.0f, 10.0f, 1.0f, 8.0f); return true; } public void createBlob(int segments, Vector3 pos, float inner, float outer, float force, float inner_force) { float angle_step = 2.0f * (float) Math.PI / (float) segments; float outer_segment_length = 2.0f * outer * (float) Math.Sin(angle_step / 2.0); float inner_segment_length = 2.0f * inner * (float) Math.Sin(angle_step / 2.0); float ring_gap = outer-inner; float yBounds = 30.0f; LineConstraint lowerLine = new LineConstraint(yBounds, LineConstraint.ConstraintType.Above); LineConstraint upperLine = new LineConstraint(-yBounds, LineConstraint.ConstraintType.Below); float xBounds = 45.0f; LineConstraint leftLine = new LineConstraint(xBounds, LineConstraint.ConstraintType.Left); LineConstraint rightLine = new LineConstraint(-xBounds, LineConstraint.ConstraintType.Right); List points = new List(); VerletPoint mid = VerletSystem.get().createPoint(pos); // create outer point (which lie on a circle) for (int i=0;i 0.0f) Game.Instance.Exit(); } private void _SetupInputMap(TorqueObject player, int playerIndex, String gamePad, String keyboard) { // Set player as the controllable object PlayerManager.Instance.GetPlayer(playerIndex).ControlObject = player; // Get input map for this player and configure it InputMap inputMap = PlayerManager.Instance.GetPlayer(playerIndex).InputMap; int gamepadId = InputManager.Instance.FindDevice(gamePad); if (gamepadId >= 0) { inputMap.BindMove(gamepadId, (int)XGamePadDevice.GamePadObjects.LeftThumbX, MoveMapTypes.StickAnalogHorizontal, 0); inputMap.BindMove(gamepadId, (int)XGamePadDevice.GamePadObjects.LeftThumbY, MoveMapTypes.StickAnalogVertical, 0); inputMap.BindAction(gamepadId, (int)XGamePadDevice.GamePadObjects.Back, _OnBackButton); } // keyboard controls int keyboardId = InputManager.Instance.FindDevice(keyboard); if (keyboardId >= 0) { inputMap.BindMove(keyboardId, (int)Keys.Right, MoveMapTypes.StickDigitalRight, 0); inputMap.BindMove(keyboardId, (int)Keys.Left, MoveMapTypes.StickDigitalLeft, 0); inputMap.BindMove(keyboardId, (int)Keys.Up, MoveMapTypes.StickDigitalUp, 0); inputMap.BindMove(keyboardId, (int)Keys.Down, MoveMapTypes.StickDigitalDown, 0); // WASD inputMap.BindMove(keyboardId, (int)Keys.D, MoveMapTypes.StickDigitalRight, 0); inputMap.BindMove(keyboardId, (int)Keys.A, MoveMapTypes.StickDigitalLeft, 0); inputMap.BindMove(keyboardId, (int)Keys.W, MoveMapTypes.StickDigitalUp, 0); inputMap.BindMove(keyboardId, (int)Keys.S, MoveMapTypes.StickDigitalDown, 0); } } #endregion //====================================================== #region Private, protected, internal fields BlobComponent _blob; int _playerNumber = 0; #endregion } }