From 07b9a1bfb5582874bf00a440bf35fd0feaa0eb64 Mon Sep 17 00:00:00 2001 From: Aymeric Date: Sun, 20 Mar 2016 23:51:00 +0100 Subject: [PATCH] Reset repository for working on V3.2.0 ! --- .gitignore | 2 + CHANGELOG.md | 289 +++ LICENSE.md | 27 + README.md | 75 + ant/README.md | 20 + ant/build.properties | 5 + ant/build.xml | 417 +++++ ce-asdoc/README.md | 1 + fla/Components.fla | Bin 0 -> 108916 bytes fla/README.md | 10 + lib/EazeTween.swc | Bin 0 -> 27715 bytes lib/feathers.swc | Bin 0 -> 807001 bytes lib/nape-release.swc | Bin 0 -> 323060 bytes lib/signals.swc | Bin 0 -> 42705 bytes lib/starling.swc | Bin 0 -> 294779 bytes src/citrus/core/CitrusEngine.as | 485 +++++ src/citrus/core/CitrusGroup.as | 144 ++ src/citrus/core/CitrusObject.as | 151 ++ src/citrus/core/Console.as | 339 ++++ src/citrus/core/IState.as | 33 + src/citrus/core/MediatorState.as | 380 ++++ src/citrus/core/State.as | 185 ++ src/citrus/core/citrus_internal.as | 7 + .../core/starling/CitrusStarlingJuggler.as | 25 + .../core/starling/StarlingCitrusEngine.as | 387 ++++ src/citrus/core/starling/StarlingState.as | 197 ++ src/citrus/core/starling/ViewportMode.as | 36 + src/citrus/datastructures/BitFlag.as | 347 ++++ src/citrus/datastructures/DoublyLinkedList.as | 210 +++ .../datastructures/DoublyLinkedListNode.as | 23 + src/citrus/datastructures/PoolObject.as | 499 ++++++ src/citrus/datastructures/Tools.as | 71 + src/citrus/events/CitrusEvent.as | 52 + src/citrus/events/CitrusEventDispatcher.as | 187 ++ src/citrus/events/CitrusSoundEvent.as | 110 ++ src/citrus/input/Input.as | 435 +++++ src/citrus/input/InputAction.as | 154 ++ src/citrus/input/InputController.as | 189 ++ src/citrus/input/InputPhase.as | 37 + .../input/controllers/AVirtualButton.as | 87 + .../input/controllers/AVirtualJoystick.as | 259 +++ src/citrus/input/controllers/Accelerometer.as | 328 ++++ src/citrus/input/controllers/Keyboard.as | 456 +++++ src/citrus/input/controllers/TimeShifter.as | 513 ++++++ .../controllers/displaylist/VirtualButton.as | 106 ++ .../displaylist/VirtualJoystick.as | 193 ++ .../gamepad/GamePadButtonRebinder.as | 148 ++ .../controllers/gamepad/GamePadManager.as | 259 +++ .../input/controllers/gamepad/Gamepad.as | 488 +++++ .../gamepad/controls/ButtonController.as | 113 ++ .../controllers/gamepad/controls/Icontrol.as | 16 + .../gamepad/controls/StickController.as | 234 +++ .../gamepad/maps/FreeboxGamepadMap.as | 46 + .../controllers/gamepad/maps/GamePadMap.as | 90 + .../gamepad/maps/OUYAGamepadMap.as | 60 + .../controllers/gamepad/maps/PS3GamepadMap.as | 93 + .../gamepad/maps/Xbox360GamepadMap.as | 98 + .../input/controllers/starling/ScreenTouch.as | 86 + .../controllers/starling/VirtualButton.as | 127 ++ .../controllers/starling/VirtualJoystick.as | 241 +++ src/citrus/math/MathUtils.as | 434 +++++ src/citrus/math/MathVector.as | 142 ++ src/citrus/math/PolarPoint.as | 200 +++ src/citrus/objects/APhysicsObject.as | 219 +++ src/citrus/objects/Box2DObjectPool.as | 117 ++ src/citrus/objects/Box2DPhysicsObject.as | 436 +++++ src/citrus/objects/CitrusObjectPool.as | 45 + src/citrus/objects/CitrusSprite.as | 296 +++ src/citrus/objects/CitrusSpritePool.as | 74 + src/citrus/objects/NapeObjectPool.as | 92 + src/citrus/objects/NapePhysicsObject.as | 367 ++++ src/citrus/objects/common/Emitter.as | 293 +++ src/citrus/objects/common/EmitterParticle.as | 22 + src/citrus/objects/common/Path.as | 107 ++ .../objects/complex/box2dstarling/Bridge.as | 208 +++ .../objects/complex/box2dstarling/FluidBox.as | 210 +++ .../objects/complex/box2dstarling/Pool.as | 175 ++ .../objects/complex/box2dstarling/Rope.as | 324 ++++ src/citrus/objects/platformer/box2d/Cannon.as | 1 + src/citrus/objects/platformer/box2d/Coin.as | 55 + src/citrus/objects/platformer/box2d/Crate.as | 29 + src/citrus/objects/platformer/box2d/Enemy.as | 222 +++ src/citrus/objects/platformer/box2d/Hero.as | 490 +++++ src/citrus/objects/platformer/box2d/Hills.as | 227 +++ .../objects/platformer/box2d/Missile.as | 178 ++ .../platformer/box2d/MovingPlatform.as | 208 +++ .../objects/platformer/box2d/Platform.as | 80 + .../platformer/box2d/RevolvingPlatform.as | 139 ++ src/citrus/objects/platformer/box2d/Reward.as | 184 ++ .../objects/platformer/box2d/RewardBox.as | 174 ++ src/citrus/objects/platformer/box2d/Sensor.as | 77 + .../objects/platformer/box2d/Teleporter.as | 1 + .../objects/platformer/box2d/Treadmill.as | 1 + src/citrus/objects/platformer/nape/Cannon.as | 154 ++ src/citrus/objects/platformer/nape/Coin.as | 46 + src/citrus/objects/platformer/nape/Crate.as | 30 + src/citrus/objects/platformer/nape/Enemy.as | 166 ++ src/citrus/objects/platformer/nape/Hero.as | 461 +++++ src/citrus/objects/platformer/nape/Hills.as | 171 ++ src/citrus/objects/platformer/nape/Missile.as | 177 ++ .../platformer/nape/MissileWithExplosion.as | 214 +++ .../objects/platformer/nape/MovingPlatform.as | 243 +++ .../objects/platformer/nape/Platform.as | 112 ++ src/citrus/objects/platformer/nape/Sensor.as | 92 + .../objects/platformer/nape/Teleporter.as | 107 ++ .../platformer/simple/DynamicObject.as | 15 + src/citrus/objects/platformer/simple/Hero.as | 52 + .../objects/platformer/simple/Sensor.as | 9 + .../objects/platformer/simple/StaticObject.as | 14 + src/citrus/objects/vehicle/nape/Car.as | 290 +++ src/citrus/objects/vehicle/nape/Driver.as | 55 + src/citrus/objects/vehicle/nape/Nugget.as | 84 + src/citrus/objects/vehicle/nape/Wheel.as | 23 + src/citrus/physics/APhysicsEngine.as | 184 ++ src/citrus/physics/IDebugView.as | 35 + .../physics/PhysicsCollisionCategories.as | 112 ++ src/citrus/physics/box2d/Box2D.as | 116 ++ .../physics/box2d/Box2DContactListener.as | 95 + src/citrus/physics/box2d/Box2DDebugArt.as | 88 + src/citrus/physics/box2d/Box2DShapeMaker.as | 51 + src/citrus/physics/box2d/Box2DUtils.as | 68 + .../physics/box2d/IBox2DPhysicsObject.as | 44 + src/citrus/physics/nape/INapePhysicsObject.as | 39 + src/citrus/physics/nape/Nape.as | 113 ++ .../physics/nape/NapeContactListener.as | 86 + src/citrus/physics/nape/NapeDebugArt.as | 147 ++ src/citrus/physics/nape/NapeUtils.as | 108 ++ .../physics/simple/SimpleCitrusSolver.as | 249 +++ src/citrus/physics/simple/SimpleCollision.as | 28 + src/citrus/sounds/CitrusSound.as | 398 +++++ src/citrus/sounds/CitrusSoundDebugArt.as | 24 + src/citrus/sounds/CitrusSoundGroup.as | 173 ++ src/citrus/sounds/CitrusSoundInstance.as | 499 ++++++ src/citrus/sounds/CitrusSoundObject.as | 288 +++ src/citrus/sounds/CitrusSoundSpace.as | 254 +++ src/citrus/sounds/SoundManager.as | 443 +++++ src/citrus/utils/AGameData.as | 141 ++ src/citrus/utils/LevelManager.as | 217 +++ src/citrus/utils/LoadManager.as | 164 ++ src/citrus/utils/Mobile.as | 123 ++ src/citrus/utils/Platform.as | 380 ++++ src/citrus/utils/SoundChannelUtil.as | 114 ++ .../utils/objectmakers/ObjectMaker2D.as | 460 +++++ .../utils/objectmakers/ObjectMaker3D.as | 109 ++ .../utils/objectmakers/ObjectMakerStarling.as | 414 +++++ src/citrus/utils/objectmakers/tmx/TmxLayer.as | 157 ++ src/citrus/utils/objectmakers/tmx/TmxMap.as | 71 + .../utils/objectmakers/tmx/TmxObject.as | 72 + .../utils/objectmakers/tmx/TmxObjectGroup.as | 41 + .../utils/objectmakers/tmx/TmxPropertySet.as | 23 + .../utils/objectmakers/tmx/TmxTileSet.as | 85 + src/citrus/view/ACitrusCamera.as | 624 +++++++ src/citrus/view/ACitrusView.as | 160 ++ src/citrus/view/ICitrusArt.as | 13 + src/citrus/view/ISpriteView.as | 172 ++ .../view/starlingview/AnimationSequence.as | 280 +++ src/citrus/view/starlingview/StarlingArt.as | 530 ++++++ .../view/starlingview/StarlingCamera.as | 532 ++++++ .../starlingview/StarlingPhysicsDebugView.as | 54 + .../starlingview/StarlingSpriteDebugArt.as | 1 + src/citrus/view/starlingview/StarlingView.as | 111 ++ srclib/Box2D/Collision/ClipVertex.as | 46 + srclib/Box2D/Collision/Features.as | 85 + srclib/Box2D/Collision/IBroadPhase.as | 80 + .../Box2D/Collision/Shapes/b2CircleShape.as | 227 +++ .../Box2D/Collision/Shapes/b2EdgeChainDef.as | 51 + srclib/Box2D/Collision/Shapes/b2EdgeShape.as | 410 +++++ srclib/Box2D/Collision/Shapes/b2MassData.as | 50 + .../Box2D/Collision/Shapes/b2PolygonShape.as | 922 ++++++++++ srclib/Box2D/Collision/Shapes/b2Shape.as | 171 ++ srclib/Box2D/Collision/b2AABB.as | 225 +++ srclib/Box2D/Collision/b2Bound.as | 51 + srclib/Box2D/Collision/b2BoundValues.as | 41 + srclib/Box2D/Collision/b2BroadPhase.as | 1089 +++++++++++ srclib/Box2D/Collision/b2Collision.as | 701 ++++++++ srclib/Box2D/Collision/b2ContactID.as | 59 + srclib/Box2D/Collision/b2ContactPoint.as | 55 + srclib/Box2D/Collision/b2Distance.as | 208 +++ srclib/Box2D/Collision/b2DistanceInput.as | 39 + srclib/Box2D/Collision/b2DistanceOutput.as | 33 + srclib/Box2D/Collision/b2DistanceProxy.as | 125 ++ srclib/Box2D/Collision/b2DynamicTree.as | 476 +++++ .../Collision/b2DynamicTreeBroadPhase.as | 200 +++ srclib/Box2D/Collision/b2DynamicTreeNode.as | 42 + srclib/Box2D/Collision/b2DynamicTreePair.as | 14 + srclib/Box2D/Collision/b2Manifold.as | 95 + srclib/Box2D/Collision/b2ManifoldPoint.as | 63 + srclib/Box2D/Collision/b2OBB.as | 42 + srclib/Box2D/Collision/b2Pair.as | 66 + srclib/Box2D/Collision/b2PairManager.as | 272 +++ srclib/Box2D/Collision/b2Point.as | 48 + srclib/Box2D/Collision/b2Proxy.as | 49 + srclib/Box2D/Collision/b2RayCastInput.as | 51 + srclib/Box2D/Collision/b2RayCastOutput.as | 42 + srclib/Box2D/Collision/b2Segment.as | 159 ++ .../Box2D/Collision/b2SeparationFunction.as | 326 ++++ srclib/Box2D/Collision/b2Simplex.as | 368 ++++ srclib/Box2D/Collision/b2SimplexCache.as | 40 + srclib/Box2D/Collision/b2SimplexVertex.as | 47 + srclib/Box2D/Collision/b2TOIInput.as | 40 + srclib/Box2D/Collision/b2TimeOfImpact.as | 242 +++ srclib/Box2D/Collision/b2WorldManifold.as | 185 ++ srclib/Box2D/Common/Math/b2Mat22.as | 149 ++ srclib/Box2D/Common/Math/b2Mat33.as | 150 ++ srclib/Box2D/Common/Math/b2Math.as | 268 +++ srclib/Box2D/Common/Math/b2Sweep.as | 104 ++ srclib/Box2D/Common/Math/b2Transform.as | 81 + srclib/Box2D/Common/Math/b2Vec2.as | 142 ++ srclib/Box2D/Common/Math/b2Vec3.as | 96 + srclib/Box2D/Common/b2Color.as | 68 + srclib/Box2D/Common/b2Settings.as | 193 ++ srclib/Box2D/Common/b2internal.as | 21 + .../Dynamics/Contacts/b2CircleContact.as | 57 + srclib/Box2D/Dynamics/Contacts/b2Contact.as | 372 ++++ .../Dynamics/Contacts/b2ContactConstraint.as | 59 + .../Contacts/b2ContactConstraintPoint.as | 46 + .../Box2D/Dynamics/Contacts/b2ContactEdge.as | 46 + .../Dynamics/Contacts/b2ContactFactory.as | 151 ++ .../Dynamics/Contacts/b2ContactRegister.as | 38 + .../Dynamics/Contacts/b2ContactResult.as | 51 + .../Dynamics/Contacts/b2ContactSolver.as | 926 ++++++++++ .../Contacts/b2EdgeAndCircleContact.as | 173 ++ .../Box2D/Dynamics/Contacts/b2NullContact.as | 35 + .../Contacts/b2PolyAndCircleContact.as | 57 + .../Dynamics/Contacts/b2PolyAndEdgeContact.as | 372 ++++ .../Dynamics/Contacts/b2PolygonContact.as | 60 + .../Contacts/b2PositionSolverManifold.as | 151 ++ .../Controllers/b2BuoyancyController.as | 143 ++ .../Controllers/b2ConstantAccelController.as | 52 + .../Controllers/b2ConstantForceController.as | 47 + .../Dynamics/Controllers/b2Controller.as | 109 ++ .../Dynamics/Controllers/b2ControllerEdge.as | 22 + .../Controllers/b2GravityController.as | 102 ++ .../Controllers/b2TensorDampingController.as | 93 + .../Box2D/Dynamics/Joints/b2DistanceJoint.as | 357 ++++ .../Dynamics/Joints/b2DistanceJointDef.as | 93 + .../Box2D/Dynamics/Joints/b2FrictionJoint.as | 297 +++ .../Dynamics/Joints/b2FrictionJointDef.as | 75 + srclib/Box2D/Dynamics/Joints/b2GearJoint.as | 356 ++++ .../Box2D/Dynamics/Joints/b2GearJointDef.as | 60 + srclib/Box2D/Dynamics/Joints/b2Jacobian.as | 55 + srclib/Box2D/Dynamics/Joints/b2Joint.as | 295 +++ srclib/Box2D/Dynamics/Joints/b2JointDef.as | 66 + srclib/Box2D/Dynamics/Joints/b2JointEdge.as | 49 + srclib/Box2D/Dynamics/Joints/b2LineJoint.as | 772 ++++++++ .../Box2D/Dynamics/Joints/b2LineJointDef.as | 110 ++ srclib/Box2D/Dynamics/Joints/b2MouseJoint.as | 291 +++ .../Box2D/Dynamics/Joints/b2MouseJointDef.as | 63 + .../Box2D/Dynamics/Joints/b2PrismaticJoint.as | 781 ++++++++ .../Dynamics/Joints/b2PrismaticJointDef.as | 116 ++ srclib/Box2D/Dynamics/Joints/b2PulleyJoint.as | 668 +++++++ .../Box2D/Dynamics/Joints/b2PulleyJointDef.as | 131 ++ .../Box2D/Dynamics/Joints/b2RevoluteJoint.as | 651 +++++++ .../Dynamics/Joints/b2RevoluteJointDef.as | 121 ++ srclib/Box2D/Dynamics/Joints/b2WeldJoint.as | 301 ++++ .../Box2D/Dynamics/Joints/b2WeldJointDef.as | 72 + srclib/Box2D/Dynamics/b2Body.as | 1361 ++++++++++++++ srclib/Box2D/Dynamics/b2BodyDef.as | 139 ++ srclib/Box2D/Dynamics/b2ContactFilter.as | 69 + srclib/Box2D/Dynamics/b2ContactImpulse.as | 38 + srclib/Box2D/Dynamics/b2ContactListener.as | 78 + srclib/Box2D/Dynamics/b2ContactManager.as | 282 +++ srclib/Box2D/Dynamics/b2DebugDraw.as | 267 +++ .../Box2D/Dynamics/b2DestructionListener.as | 49 + srclib/Box2D/Dynamics/b2FilterData.as | 61 + srclib/Box2D/Dynamics/b2Fixture.as | 367 ++++ srclib/Box2D/Dynamics/b2FixtureDef.as | 88 + srclib/Box2D/Dynamics/b2Island.as | 501 ++++++ srclib/Box2D/Dynamics/b2TimeStep.as | 46 + srclib/Box2D/Dynamics/b2World.as | 1588 +++++++++++++++++ srclib/dragonBones/Armature.as | 720 ++++++++ srclib/dragonBones/Bone.as | 665 +++++++ srclib/dragonBones/Slot.as | 578 ++++++ srclib/dragonBones/animation/Animation.as | 535 ++++++ .../dragonBones/animation/AnimationState.as | 995 +++++++++++ srclib/dragonBones/animation/IAnimatable.as | 23 + .../animation/SlotTimelineState.as | 552 ++++++ srclib/dragonBones/animation/TimelineState.as | 613 +++++++ srclib/dragonBones/animation/WorldClock.as | 143 ++ srclib/dragonBones/cache/AnimationCache.as | 132 ++ .../cache/AnimationCacheManager.as | 163 ++ srclib/dragonBones/cache/FrameCache.as | 31 + srclib/dragonBones/cache/SlotFrameCache.as | 30 + srclib/dragonBones/cache/SlotTimelineCache.as | 27 + srclib/dragonBones/cache/TimelineCache.as | 27 + srclib/dragonBones/core/DBObject.as | 226 +++ srclib/dragonBones/core/DragonBones.as | 14 + srclib/dragonBones/core/IAnimationState.as | 7 + srclib/dragonBones/core/IArmature.as | 11 + srclib/dragonBones/core/ICacheUser.as | 11 + srclib/dragonBones/core/ICacheableArmature.as | 13 + .../dragonBones/core/ISlotCacheGenerator.as | 16 + .../dragonBones/core/dragonBones_internal.as | 6 + srclib/dragonBones/display/NativeFastSlot.as | 146 ++ srclib/dragonBones/display/NativeSlot.as | 145 ++ .../dragonBones/display/StarlingFastSlot.as | 224 +++ srclib/dragonBones/display/StarlingSlot.as | 223 +++ srclib/dragonBones/events/AnimationEvent.as | 111 ++ srclib/dragonBones/events/ArmatureEvent.as | 38 + srclib/dragonBones/events/FrameEvent.as | 81 + srclib/dragonBones/events/SoundEvent.as | 58 + .../dragonBones/events/SoundEventManager.as | 33 + srclib/dragonBones/factories/BaseFactory.as | 774 ++++++++ srclib/dragonBones/factories/NativeFactory.as | 168 ++ .../dragonBones/factories/StarlingFactory.as | 188 ++ srclib/dragonBones/fast/FastArmature.as | 543 ++++++ srclib/dragonBones/fast/FastBone.as | 185 ++ srclib/dragonBones/fast/FastDBObject.as | 260 +++ srclib/dragonBones/fast/FastSlot.as | 539 ++++++ .../fast/animation/FastAnimation.as | 275 +++ .../fast/animation/FastAnimationState.as | 504 ++++++ .../fast/animation/FastBoneTimelineState.as | 444 +++++ .../fast/animation/FastSlotTimelineState.as | 511 ++++++ srclib/dragonBones/objects/AnimationData.as | 128 ++ srclib/dragonBones/objects/ArmatureData.as | 324 ++++ srclib/dragonBones/objects/BoneData.as | 85 + srclib/dragonBones/objects/CurveData.as | 93 + srclib/dragonBones/objects/DBTransform.as | 131 ++ srclib/dragonBones/objects/DataParser.as | 35 + srclib/dragonBones/objects/DataSerializer.as | 111 ++ .../dragonBones/objects/DecompressedData.as | 84 + srclib/dragonBones/objects/DisplayData.as | 29 + srclib/dragonBones/objects/DragonBonesData.as | 118 ++ srclib/dragonBones/objects/EllipseData.as | 28 + srclib/dragonBones/objects/Frame.as | 24 + srclib/dragonBones/objects/IAreaData.as | 7 + .../dragonBones/objects/Object3DataParser.as | 565 ++++++ .../dragonBones/objects/ObjectDataParser.as | 476 +++++ srclib/dragonBones/objects/RectangleData.as | 28 + srclib/dragonBones/objects/SkinData.as | 64 + srclib/dragonBones/objects/SlotData.as | 63 + srclib/dragonBones/objects/SlotFrame.as | 45 + srclib/dragonBones/objects/SlotTimeline.as | 21 + .../objects/TextureAtlasByteArrayLoader.as | 24 + srclib/dragonBones/objects/Timeline.as | 53 + srclib/dragonBones/objects/TransformFrame.as | 47 + .../dragonBones/objects/TransformTimeline.as | 38 + srclib/dragonBones/objects/XML3DataParser.as | 516 ++++++ srclib/dragonBones/objects/XMLDataParser.as | 476 +++++ srclib/dragonBones/textures/ITextureAtlas.as | 31 + .../textures/NativeTextureAtlas.as | 154 ++ .../textures/StarlingTextureAtlas.as | 118 ++ srclib/dragonBones/textures/TextureData.as | 18 + srclib/dragonBones/utils/BytesType.as | 46 + .../dragonBones/utils/ColorTransformUtil.as | 43 + srclib/dragonBones/utils/ConstValues.as | 89 + srclib/dragonBones/utils/DBDataUtil.as | 357 ++++ srclib/dragonBones/utils/FactoryUtils.as | 11 + srclib/dragonBones/utils/MathUtil.as | 29 + srclib/dragonBones/utils/TransformUtil.as | 102 ++ .../extensions/filters/ThresholdFilter.as | 107 ++ .../extensions/particles/ColorArgb.as | 84 + .../extensions/particles/PDParticle.as | 32 + .../extensions/particles/PDParticleSystem.as | 419 +++++ .../starling/extensions/particles/Particle.as | 31 + .../extensions/particles/ParticleSystem.as | 521 ++++++ .../extensions/textureAtlas/DynamicAtlas.as | 582 ++++++ .../extensions/textureAtlas/TextureItem.as | 66 + srclib/starling/extensions/utils/Line.as | 51 + 359 files changed, 66777 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 ant/README.md create mode 100644 ant/build.properties create mode 100644 ant/build.xml create mode 100644 ce-asdoc/README.md create mode 100644 fla/Components.fla create mode 100644 fla/README.md create mode 100644 lib/EazeTween.swc create mode 100644 lib/feathers.swc create mode 100644 lib/nape-release.swc create mode 100644 lib/signals.swc create mode 100644 lib/starling.swc create mode 100644 src/citrus/core/CitrusEngine.as create mode 100644 src/citrus/core/CitrusGroup.as create mode 100644 src/citrus/core/CitrusObject.as create mode 100644 src/citrus/core/Console.as create mode 100644 src/citrus/core/IState.as create mode 100644 src/citrus/core/MediatorState.as create mode 100644 src/citrus/core/State.as create mode 100644 src/citrus/core/citrus_internal.as create mode 100644 src/citrus/core/starling/CitrusStarlingJuggler.as create mode 100644 src/citrus/core/starling/StarlingCitrusEngine.as create mode 100644 src/citrus/core/starling/StarlingState.as create mode 100644 src/citrus/core/starling/ViewportMode.as create mode 100644 src/citrus/datastructures/BitFlag.as create mode 100644 src/citrus/datastructures/DoublyLinkedList.as create mode 100644 src/citrus/datastructures/DoublyLinkedListNode.as create mode 100644 src/citrus/datastructures/PoolObject.as create mode 100644 src/citrus/datastructures/Tools.as create mode 100644 src/citrus/events/CitrusEvent.as create mode 100644 src/citrus/events/CitrusEventDispatcher.as create mode 100644 src/citrus/events/CitrusSoundEvent.as create mode 100644 src/citrus/input/Input.as create mode 100644 src/citrus/input/InputAction.as create mode 100644 src/citrus/input/InputController.as create mode 100644 src/citrus/input/InputPhase.as create mode 100644 src/citrus/input/controllers/AVirtualButton.as create mode 100644 src/citrus/input/controllers/AVirtualJoystick.as create mode 100644 src/citrus/input/controllers/Accelerometer.as create mode 100644 src/citrus/input/controllers/Keyboard.as create mode 100644 src/citrus/input/controllers/TimeShifter.as create mode 100644 src/citrus/input/controllers/displaylist/VirtualButton.as create mode 100644 src/citrus/input/controllers/displaylist/VirtualJoystick.as create mode 100644 src/citrus/input/controllers/gamepad/GamePadButtonRebinder.as create mode 100644 src/citrus/input/controllers/gamepad/GamePadManager.as create mode 100644 src/citrus/input/controllers/gamepad/Gamepad.as create mode 100644 src/citrus/input/controllers/gamepad/controls/ButtonController.as create mode 100644 src/citrus/input/controllers/gamepad/controls/Icontrol.as create mode 100644 src/citrus/input/controllers/gamepad/controls/StickController.as create mode 100644 src/citrus/input/controllers/gamepad/maps/FreeboxGamepadMap.as create mode 100644 src/citrus/input/controllers/gamepad/maps/GamePadMap.as create mode 100644 src/citrus/input/controllers/gamepad/maps/OUYAGamepadMap.as create mode 100644 src/citrus/input/controllers/gamepad/maps/PS3GamepadMap.as create mode 100644 src/citrus/input/controllers/gamepad/maps/Xbox360GamepadMap.as create mode 100644 src/citrus/input/controllers/starling/ScreenTouch.as create mode 100644 src/citrus/input/controllers/starling/VirtualButton.as create mode 100644 src/citrus/input/controllers/starling/VirtualJoystick.as create mode 100644 src/citrus/math/MathUtils.as create mode 100644 src/citrus/math/MathVector.as create mode 100644 src/citrus/math/PolarPoint.as create mode 100644 src/citrus/objects/APhysicsObject.as create mode 100644 src/citrus/objects/Box2DObjectPool.as create mode 100644 src/citrus/objects/Box2DPhysicsObject.as create mode 100644 src/citrus/objects/CitrusObjectPool.as create mode 100644 src/citrus/objects/CitrusSprite.as create mode 100644 src/citrus/objects/CitrusSpritePool.as create mode 100644 src/citrus/objects/NapeObjectPool.as create mode 100644 src/citrus/objects/NapePhysicsObject.as create mode 100644 src/citrus/objects/common/Emitter.as create mode 100644 src/citrus/objects/common/EmitterParticle.as create mode 100644 src/citrus/objects/common/Path.as create mode 100644 src/citrus/objects/complex/box2dstarling/Bridge.as create mode 100644 src/citrus/objects/complex/box2dstarling/FluidBox.as create mode 100644 src/citrus/objects/complex/box2dstarling/Pool.as create mode 100644 src/citrus/objects/complex/box2dstarling/Rope.as create mode 100644 src/citrus/objects/platformer/box2d/Cannon.as create mode 100644 src/citrus/objects/platformer/box2d/Coin.as create mode 100644 src/citrus/objects/platformer/box2d/Crate.as create mode 100644 src/citrus/objects/platformer/box2d/Enemy.as create mode 100644 src/citrus/objects/platformer/box2d/Hero.as create mode 100644 src/citrus/objects/platformer/box2d/Hills.as create mode 100644 src/citrus/objects/platformer/box2d/Missile.as create mode 100644 src/citrus/objects/platformer/box2d/MovingPlatform.as create mode 100644 src/citrus/objects/platformer/box2d/Platform.as create mode 100644 src/citrus/objects/platformer/box2d/RevolvingPlatform.as create mode 100644 src/citrus/objects/platformer/box2d/Reward.as create mode 100644 src/citrus/objects/platformer/box2d/RewardBox.as create mode 100644 src/citrus/objects/platformer/box2d/Sensor.as create mode 100644 src/citrus/objects/platformer/box2d/Teleporter.as create mode 100644 src/citrus/objects/platformer/box2d/Treadmill.as create mode 100644 src/citrus/objects/platformer/nape/Cannon.as create mode 100644 src/citrus/objects/platformer/nape/Coin.as create mode 100644 src/citrus/objects/platformer/nape/Crate.as create mode 100644 src/citrus/objects/platformer/nape/Enemy.as create mode 100644 src/citrus/objects/platformer/nape/Hero.as create mode 100644 src/citrus/objects/platformer/nape/Hills.as create mode 100644 src/citrus/objects/platformer/nape/Missile.as create mode 100644 src/citrus/objects/platformer/nape/MissileWithExplosion.as create mode 100644 src/citrus/objects/platformer/nape/MovingPlatform.as create mode 100644 src/citrus/objects/platformer/nape/Platform.as create mode 100644 src/citrus/objects/platformer/nape/Sensor.as create mode 100644 src/citrus/objects/platformer/nape/Teleporter.as create mode 100644 src/citrus/objects/platformer/simple/DynamicObject.as create mode 100644 src/citrus/objects/platformer/simple/Hero.as create mode 100644 src/citrus/objects/platformer/simple/Sensor.as create mode 100644 src/citrus/objects/platformer/simple/StaticObject.as create mode 100644 src/citrus/objects/vehicle/nape/Car.as create mode 100644 src/citrus/objects/vehicle/nape/Driver.as create mode 100644 src/citrus/objects/vehicle/nape/Nugget.as create mode 100644 src/citrus/objects/vehicle/nape/Wheel.as create mode 100644 src/citrus/physics/APhysicsEngine.as create mode 100644 src/citrus/physics/IDebugView.as create mode 100644 src/citrus/physics/PhysicsCollisionCategories.as create mode 100644 src/citrus/physics/box2d/Box2D.as create mode 100644 src/citrus/physics/box2d/Box2DContactListener.as create mode 100644 src/citrus/physics/box2d/Box2DDebugArt.as create mode 100644 src/citrus/physics/box2d/Box2DShapeMaker.as create mode 100644 src/citrus/physics/box2d/Box2DUtils.as create mode 100644 src/citrus/physics/box2d/IBox2DPhysicsObject.as create mode 100644 src/citrus/physics/nape/INapePhysicsObject.as create mode 100644 src/citrus/physics/nape/Nape.as create mode 100644 src/citrus/physics/nape/NapeContactListener.as create mode 100644 src/citrus/physics/nape/NapeDebugArt.as create mode 100644 src/citrus/physics/nape/NapeUtils.as create mode 100644 src/citrus/physics/simple/SimpleCitrusSolver.as create mode 100644 src/citrus/physics/simple/SimpleCollision.as create mode 100644 src/citrus/sounds/CitrusSound.as create mode 100644 src/citrus/sounds/CitrusSoundDebugArt.as create mode 100644 src/citrus/sounds/CitrusSoundGroup.as create mode 100644 src/citrus/sounds/CitrusSoundInstance.as create mode 100644 src/citrus/sounds/CitrusSoundObject.as create mode 100644 src/citrus/sounds/CitrusSoundSpace.as create mode 100644 src/citrus/sounds/SoundManager.as create mode 100644 src/citrus/utils/AGameData.as create mode 100644 src/citrus/utils/LevelManager.as create mode 100644 src/citrus/utils/LoadManager.as create mode 100644 src/citrus/utils/Mobile.as create mode 100644 src/citrus/utils/Platform.as create mode 100644 src/citrus/utils/SoundChannelUtil.as create mode 100644 src/citrus/utils/objectmakers/ObjectMaker2D.as create mode 100644 src/citrus/utils/objectmakers/ObjectMaker3D.as create mode 100644 src/citrus/utils/objectmakers/ObjectMakerStarling.as create mode 100644 src/citrus/utils/objectmakers/tmx/TmxLayer.as create mode 100644 src/citrus/utils/objectmakers/tmx/TmxMap.as create mode 100644 src/citrus/utils/objectmakers/tmx/TmxObject.as create mode 100644 src/citrus/utils/objectmakers/tmx/TmxObjectGroup.as create mode 100644 src/citrus/utils/objectmakers/tmx/TmxPropertySet.as create mode 100644 src/citrus/utils/objectmakers/tmx/TmxTileSet.as create mode 100644 src/citrus/view/ACitrusCamera.as create mode 100644 src/citrus/view/ACitrusView.as create mode 100644 src/citrus/view/ICitrusArt.as create mode 100644 src/citrus/view/ISpriteView.as create mode 100644 src/citrus/view/starlingview/AnimationSequence.as create mode 100644 src/citrus/view/starlingview/StarlingArt.as create mode 100644 src/citrus/view/starlingview/StarlingCamera.as create mode 100644 src/citrus/view/starlingview/StarlingPhysicsDebugView.as create mode 100644 src/citrus/view/starlingview/StarlingSpriteDebugArt.as create mode 100644 src/citrus/view/starlingview/StarlingView.as create mode 100644 srclib/Box2D/Collision/ClipVertex.as create mode 100644 srclib/Box2D/Collision/Features.as create mode 100644 srclib/Box2D/Collision/IBroadPhase.as create mode 100644 srclib/Box2D/Collision/Shapes/b2CircleShape.as create mode 100644 srclib/Box2D/Collision/Shapes/b2EdgeChainDef.as create mode 100644 srclib/Box2D/Collision/Shapes/b2EdgeShape.as create mode 100644 srclib/Box2D/Collision/Shapes/b2MassData.as create mode 100644 srclib/Box2D/Collision/Shapes/b2PolygonShape.as create mode 100644 srclib/Box2D/Collision/Shapes/b2Shape.as create mode 100644 srclib/Box2D/Collision/b2AABB.as create mode 100644 srclib/Box2D/Collision/b2Bound.as create mode 100644 srclib/Box2D/Collision/b2BoundValues.as create mode 100644 srclib/Box2D/Collision/b2BroadPhase.as create mode 100644 srclib/Box2D/Collision/b2Collision.as create mode 100644 srclib/Box2D/Collision/b2ContactID.as create mode 100644 srclib/Box2D/Collision/b2ContactPoint.as create mode 100644 srclib/Box2D/Collision/b2Distance.as create mode 100644 srclib/Box2D/Collision/b2DistanceInput.as create mode 100644 srclib/Box2D/Collision/b2DistanceOutput.as create mode 100644 srclib/Box2D/Collision/b2DistanceProxy.as create mode 100644 srclib/Box2D/Collision/b2DynamicTree.as create mode 100644 srclib/Box2D/Collision/b2DynamicTreeBroadPhase.as create mode 100644 srclib/Box2D/Collision/b2DynamicTreeNode.as create mode 100644 srclib/Box2D/Collision/b2DynamicTreePair.as create mode 100644 srclib/Box2D/Collision/b2Manifold.as create mode 100644 srclib/Box2D/Collision/b2ManifoldPoint.as create mode 100644 srclib/Box2D/Collision/b2OBB.as create mode 100644 srclib/Box2D/Collision/b2Pair.as create mode 100644 srclib/Box2D/Collision/b2PairManager.as create mode 100644 srclib/Box2D/Collision/b2Point.as create mode 100644 srclib/Box2D/Collision/b2Proxy.as create mode 100644 srclib/Box2D/Collision/b2RayCastInput.as create mode 100644 srclib/Box2D/Collision/b2RayCastOutput.as create mode 100644 srclib/Box2D/Collision/b2Segment.as create mode 100644 srclib/Box2D/Collision/b2SeparationFunction.as create mode 100644 srclib/Box2D/Collision/b2Simplex.as create mode 100644 srclib/Box2D/Collision/b2SimplexCache.as create mode 100644 srclib/Box2D/Collision/b2SimplexVertex.as create mode 100644 srclib/Box2D/Collision/b2TOIInput.as create mode 100644 srclib/Box2D/Collision/b2TimeOfImpact.as create mode 100644 srclib/Box2D/Collision/b2WorldManifold.as create mode 100644 srclib/Box2D/Common/Math/b2Mat22.as create mode 100644 srclib/Box2D/Common/Math/b2Mat33.as create mode 100644 srclib/Box2D/Common/Math/b2Math.as create mode 100644 srclib/Box2D/Common/Math/b2Sweep.as create mode 100644 srclib/Box2D/Common/Math/b2Transform.as create mode 100644 srclib/Box2D/Common/Math/b2Vec2.as create mode 100644 srclib/Box2D/Common/Math/b2Vec3.as create mode 100644 srclib/Box2D/Common/b2Color.as create mode 100644 srclib/Box2D/Common/b2Settings.as create mode 100644 srclib/Box2D/Common/b2internal.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2CircleContact.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2Contact.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2ContactConstraint.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2ContactConstraintPoint.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2ContactEdge.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2ContactFactory.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2ContactRegister.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2ContactResult.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2ContactSolver.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2EdgeAndCircleContact.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2NullContact.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2PolyAndCircleContact.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2PolyAndEdgeContact.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2PolygonContact.as create mode 100644 srclib/Box2D/Dynamics/Contacts/b2PositionSolverManifold.as create mode 100644 srclib/Box2D/Dynamics/Controllers/b2BuoyancyController.as create mode 100644 srclib/Box2D/Dynamics/Controllers/b2ConstantAccelController.as create mode 100644 srclib/Box2D/Dynamics/Controllers/b2ConstantForceController.as create mode 100644 srclib/Box2D/Dynamics/Controllers/b2Controller.as create mode 100644 srclib/Box2D/Dynamics/Controllers/b2ControllerEdge.as create mode 100644 srclib/Box2D/Dynamics/Controllers/b2GravityController.as create mode 100644 srclib/Box2D/Dynamics/Controllers/b2TensorDampingController.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2DistanceJoint.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2DistanceJointDef.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2FrictionJoint.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2FrictionJointDef.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2GearJoint.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2GearJointDef.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2Jacobian.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2Joint.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2JointDef.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2JointEdge.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2LineJoint.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2LineJointDef.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2MouseJoint.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2MouseJointDef.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2PrismaticJoint.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2PrismaticJointDef.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2PulleyJoint.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2PulleyJointDef.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2RevoluteJoint.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2RevoluteJointDef.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2WeldJoint.as create mode 100644 srclib/Box2D/Dynamics/Joints/b2WeldJointDef.as create mode 100644 srclib/Box2D/Dynamics/b2Body.as create mode 100644 srclib/Box2D/Dynamics/b2BodyDef.as create mode 100644 srclib/Box2D/Dynamics/b2ContactFilter.as create mode 100644 srclib/Box2D/Dynamics/b2ContactImpulse.as create mode 100644 srclib/Box2D/Dynamics/b2ContactListener.as create mode 100644 srclib/Box2D/Dynamics/b2ContactManager.as create mode 100644 srclib/Box2D/Dynamics/b2DebugDraw.as create mode 100644 srclib/Box2D/Dynamics/b2DestructionListener.as create mode 100644 srclib/Box2D/Dynamics/b2FilterData.as create mode 100644 srclib/Box2D/Dynamics/b2Fixture.as create mode 100644 srclib/Box2D/Dynamics/b2FixtureDef.as create mode 100644 srclib/Box2D/Dynamics/b2Island.as create mode 100644 srclib/Box2D/Dynamics/b2TimeStep.as create mode 100644 srclib/Box2D/Dynamics/b2World.as create mode 100644 srclib/dragonBones/Armature.as create mode 100644 srclib/dragonBones/Bone.as create mode 100644 srclib/dragonBones/Slot.as create mode 100644 srclib/dragonBones/animation/Animation.as create mode 100644 srclib/dragonBones/animation/AnimationState.as create mode 100644 srclib/dragonBones/animation/IAnimatable.as create mode 100644 srclib/dragonBones/animation/SlotTimelineState.as create mode 100644 srclib/dragonBones/animation/TimelineState.as create mode 100644 srclib/dragonBones/animation/WorldClock.as create mode 100644 srclib/dragonBones/cache/AnimationCache.as create mode 100644 srclib/dragonBones/cache/AnimationCacheManager.as create mode 100644 srclib/dragonBones/cache/FrameCache.as create mode 100644 srclib/dragonBones/cache/SlotFrameCache.as create mode 100644 srclib/dragonBones/cache/SlotTimelineCache.as create mode 100644 srclib/dragonBones/cache/TimelineCache.as create mode 100644 srclib/dragonBones/core/DBObject.as create mode 100644 srclib/dragonBones/core/DragonBones.as create mode 100644 srclib/dragonBones/core/IAnimationState.as create mode 100644 srclib/dragonBones/core/IArmature.as create mode 100644 srclib/dragonBones/core/ICacheUser.as create mode 100644 srclib/dragonBones/core/ICacheableArmature.as create mode 100644 srclib/dragonBones/core/ISlotCacheGenerator.as create mode 100644 srclib/dragonBones/core/dragonBones_internal.as create mode 100644 srclib/dragonBones/display/NativeFastSlot.as create mode 100644 srclib/dragonBones/display/NativeSlot.as create mode 100644 srclib/dragonBones/display/StarlingFastSlot.as create mode 100644 srclib/dragonBones/display/StarlingSlot.as create mode 100644 srclib/dragonBones/events/AnimationEvent.as create mode 100644 srclib/dragonBones/events/ArmatureEvent.as create mode 100644 srclib/dragonBones/events/FrameEvent.as create mode 100644 srclib/dragonBones/events/SoundEvent.as create mode 100644 srclib/dragonBones/events/SoundEventManager.as create mode 100644 srclib/dragonBones/factories/BaseFactory.as create mode 100644 srclib/dragonBones/factories/NativeFactory.as create mode 100644 srclib/dragonBones/factories/StarlingFactory.as create mode 100644 srclib/dragonBones/fast/FastArmature.as create mode 100644 srclib/dragonBones/fast/FastBone.as create mode 100644 srclib/dragonBones/fast/FastDBObject.as create mode 100644 srclib/dragonBones/fast/FastSlot.as create mode 100644 srclib/dragonBones/fast/animation/FastAnimation.as create mode 100644 srclib/dragonBones/fast/animation/FastAnimationState.as create mode 100644 srclib/dragonBones/fast/animation/FastBoneTimelineState.as create mode 100644 srclib/dragonBones/fast/animation/FastSlotTimelineState.as create mode 100644 srclib/dragonBones/objects/AnimationData.as create mode 100644 srclib/dragonBones/objects/ArmatureData.as create mode 100644 srclib/dragonBones/objects/BoneData.as create mode 100644 srclib/dragonBones/objects/CurveData.as create mode 100644 srclib/dragonBones/objects/DBTransform.as create mode 100644 srclib/dragonBones/objects/DataParser.as create mode 100644 srclib/dragonBones/objects/DataSerializer.as create mode 100644 srclib/dragonBones/objects/DecompressedData.as create mode 100644 srclib/dragonBones/objects/DisplayData.as create mode 100644 srclib/dragonBones/objects/DragonBonesData.as create mode 100644 srclib/dragonBones/objects/EllipseData.as create mode 100644 srclib/dragonBones/objects/Frame.as create mode 100644 srclib/dragonBones/objects/IAreaData.as create mode 100644 srclib/dragonBones/objects/Object3DataParser.as create mode 100644 srclib/dragonBones/objects/ObjectDataParser.as create mode 100644 srclib/dragonBones/objects/RectangleData.as create mode 100644 srclib/dragonBones/objects/SkinData.as create mode 100644 srclib/dragonBones/objects/SlotData.as create mode 100644 srclib/dragonBones/objects/SlotFrame.as create mode 100644 srclib/dragonBones/objects/SlotTimeline.as create mode 100644 srclib/dragonBones/objects/TextureAtlasByteArrayLoader.as create mode 100644 srclib/dragonBones/objects/Timeline.as create mode 100644 srclib/dragonBones/objects/TransformFrame.as create mode 100644 srclib/dragonBones/objects/TransformTimeline.as create mode 100644 srclib/dragonBones/objects/XML3DataParser.as create mode 100644 srclib/dragonBones/objects/XMLDataParser.as create mode 100644 srclib/dragonBones/textures/ITextureAtlas.as create mode 100644 srclib/dragonBones/textures/NativeTextureAtlas.as create mode 100644 srclib/dragonBones/textures/StarlingTextureAtlas.as create mode 100644 srclib/dragonBones/textures/TextureData.as create mode 100644 srclib/dragonBones/utils/BytesType.as create mode 100644 srclib/dragonBones/utils/ColorTransformUtil.as create mode 100644 srclib/dragonBones/utils/ConstValues.as create mode 100644 srclib/dragonBones/utils/DBDataUtil.as create mode 100644 srclib/dragonBones/utils/FactoryUtils.as create mode 100644 srclib/dragonBones/utils/MathUtil.as create mode 100644 srclib/dragonBones/utils/TransformUtil.as create mode 100644 srclib/starling/extensions/filters/ThresholdFilter.as create mode 100644 srclib/starling/extensions/particles/ColorArgb.as create mode 100644 srclib/starling/extensions/particles/PDParticle.as create mode 100644 srclib/starling/extensions/particles/PDParticleSystem.as create mode 100644 srclib/starling/extensions/particles/Particle.as create mode 100644 srclib/starling/extensions/particles/ParticleSystem.as create mode 100644 srclib/starling/extensions/textureAtlas/DynamicAtlas.as create mode 100644 srclib/starling/extensions/textureAtlas/TextureItem.as create mode 100644 srclib/starling/extensions/utils/Line.as diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a5a68e4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.settings +.project \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..25ff5fa7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,289 @@ +V2.0.0, Work in Progress +------------------------ + +V3.1.12, 03 18 2016 +------------------- +- Starling updated on 1.8 +- Feathers updated on 2.3.0 +- DragonBones updated on 4.1 + +V3.1.11, 07 24 2015 +------------------- +- Starling updated on 1.7 +- Feathers updated on 2.1.2 +- Starling.handleLostContext is always set to true +- bug fix + +V3.1.10, 09 15 2014 +------------------- +- Starling updated on 1.5.1 +- DragonBones updated on 3.0 +- Nape updated on 2.0.16 +- Feathers updated 1.3.1 +- Set up SoundMixer.audioPlaybackMode to "ambient" (on iOS if the physical button is off, mute the sound). +- setUpStarling method has a stage3D argument, useful for shared context. +- Citrus has its own pausable starling juggler, AnimationSequences will be attached to it by default. When CE is paused, starling keeps running but anything attached to the citrus juggler will be paused as expected. +- CitrusSoundGroup : you can stop all sounds from a group with CitrusSoundGroup.stopAllSounds +- Camera : fixed intersects/containsRect when using rotation/zoom. +- All CitrusObjects with a view now have a reference to their actual art object. +- setUpStarling : you can force starling to run on a specific stage3D with the stage3D argument of setupStarling (used in a shared context scenario) + +V3.1.9, 03 05 2014 +------------------ +- Feathers updated on 1.2.0 +- DragonBones updated on 2.4.1 +- CitrusEngine.handlePlayingChange can be overriden to modify the default behavior of CE (input resetting as well as audio pause/resume). +- LevelManager enables SWF caching, very useful on iOS. Set the new property enableSwfCaching to true. +- GamePadMap.devPlatform : force a platform value to use on setups (to simulate other platforms such as WIN/MAC/LNX/AND) +- Gamepad.debug is a static boolean. +- CitrusSoundEvents moved to citrus.events package and fixed. +- Introducing CitrusEngine.transformMatrix which when using StarlingCitrusEngine describes the transformation matrix necessary to go from native stage to starling stage (includes viewport translation as well as starling stage scale). Concatenated with the camera transform matrix, its possible to simply project a point from/to the game space to/from native stage. +- Input's action lists reset when playing changes. +- in a flash State , .swf views become spriteview.AnimationSequences +- ISpriteView.handleArtReady receives newly created art (*Art object) after view assigned or loaded from url, handleArtChanged similarly after view changes during the game. +- APhysicsObject now has a public "animation" setter. +- AGameData is a flash Proxy class so properties can be created anytime without having to extend it (and a signal is still fired on data change). AgGameData init can be used to reset values. +- Actions : removed "duck" action for "down". +- MathUtils - added log of base N, line/segment intersection function +- Fixed/Optimized Box2DPhysicsObject's rotation +- Camera.setUp signature changed + offset became center (see wiki) +- many fixes. + +V3.1.8, 11 13 2013 +------------------ +- Starling updated on 1.4.1 +- Feathers updated on 1.1.1 +- DragonBones updated to 2.4 +- Nape updated on 2.0.12 +- Removed Spine runtime support. Use it via DragonBones library (easier to maintain only one skeleton lib). +- Updated to latest DynamicTextureAtlas extension. +- StarlingCitrusEngine offers to manage multi-resolutions (http://wiki.starling-framework.org/citrus/multi_resolution) +- ISpriteView handleArtReady/handleArtChanged - custom citrus sprite or physics objects can now react when 'the view is set (or loaded if the view was a url) or changes' to add/remove event listeners, transform the art or manipulate its content... +- Input system : justDid,isDoing,hasDone return the corresponding InputAction object or null (instead of just true or false) // actions carry messages // introduced new utility functions such as getAction() with phase/controller/channel filtering. +- Changed the way we handle the physics engine's debug drawers (due to state transition bugs, and the new nape ShapeDebug) see APhysicsEngine.debugView +- Input : removed backwards compatibility, InputActions store the time (in frames) they spent in the Input system +- MathVector : critical bug fixes (rotate, angle) and new methods (dot product, normalize, copyFrom, setTo) +- Camera : AABB rectangle is accessible using camera.getRect() +- Camera : update call can be disabled (permanently or temporarily) using camera.enabled = false - for better peformance +- Camera : added contains(x,y) , containsRect and intersectsRect as a way to know if points/objects are on or off screen and how - for objects, use their visual bounds in state space. +- Camera : added switchToTarget() to tween camera movement and switch the camera's target value +- Camera : use center instead of offset. center defines a multiple of the screen position (1,1) meaning bottom right, to decide on the camera's center position (or formerly named offset) 0.5,0.5 being the center +- SoundManager : removeAllSounds accepts exceptions, fixed stack underflow error +- SoundManager : UI sound group added by default. +- pause/resume sounds depending Event.ACTIVATE & Event.DEACTIVATE +- AnimationSequence : removeAllAnimations method +- AnimationSequence : addTextureAtlasWithAnimations to support AssetManager objects +- using starling, an animation loaded from a .swf will be transformed into an AnimationSequence using AnimationSequence.fromMovieClip +- use addEntity instead of add to add entity to state. +- nape Platform oneWay fixed. +- box2D Reward fixed (updates by default). +- Added a LoaderContext for SpriteArt and StarlingArt, we are able to load swf on iOS. +- Added rotation parameter in TmxObject coming from latest Tiled Map Editor builds. +- Fixed a bug where using StarlingCitrusEngine we had to set it up directly. +- state.getObject* functions include results from searches in the pool objects. + +V 3.1.7, 06 27 2013 +------------------- +- Updated on Feathers 1.1.0. +- Updated on Nape 2.0.9. +- Updated on DragonBones 2.2. +- Added Spine 2D skeleton library support. +- Added DragonBones support for the display list. +- SoundManager reworked with a CitrusSound class and CitrusSoundGroup. +- PoolObject reworked. +- Camera reworked. +- Added a vehicle package running with Nape composed with a chassis, a driver, two wheels and some nuggets! +- Added support for pure state transition (having two state at the same time) using futureState. +- Changed the way the viewport is setup by default (based on Capabilities.playerType now). +- Starling.handleLostContext is defined to true if you use Android, made in setUpStarling function. +- if StarlingArt updateArtEnabled is set to false, it will flatten the Sprite. +- StarlingArt may handle an uint color, it will automatically create a quad. +- AnimationSequence textureAtlas could be an AssetManager object. +- You may add a MovieClip to an AnimationSequence. +- added stopAllPlayingSounds(...except) method. +- added removeAllSounds(...except) method. +- removeSound has a new argument : stopSoundIfPlaying:Boolean = false. +- Emitters have their updateCallEnabled = true; +- SoundManager fix: stream sound directly after load(); when sound was added as an url. +- fixed: stopSound wasn't setting the playing var to false. +- fixed: InputComponent wasn't setting isDoingLeft. +- fixed: updateCombinedGroundAngle() on box2d Hero +- fixed count in DoublyLinkedList if removeNode is called. +- throwing an error if the Main class doesn't extends StarlingCitrusEngine or didn't call setUpStarling + +V 3.1.6, 04 18 2013 +------------------- +- Updated on Nape 2.0.8 +- Updated on DragonBones 2.1.1 +- Mouse/Touch are disable on objects to save performances, use touchable new property to be able to interact with touch/mouse on the object. +- Box2D contact provided by handleBeginContact, handleEndContact... uses the worldManifold instead of the local (made collision management easier). +- An entity uses a Vector to store components instead of a Dictionary. +- ObjectMakerStarling FromMovieClip's function allow to use an AssetManager object! + +V 3.1.5, 04 15 2013 +------------------- +- SWCs include comments! +- Added EazeTween as the default tweening engine. +- Update on DragonBones V2.0 +- No more duplicated code between States class, all use the same basis: MediatorState. Now States class are just wrapper. +- When Starling is set up it picks up fullScreen dimension if it's running on mobile. The Context3DProfile parameter is also added. +- Added updateCallEnabled property to CitrusObject: This property prevent the update method to be called by the enter frame, it will save performances. Set it to true if you want to execute code in the update method. +- Added updateArtEnabled property to Art object. Set it to false if you want to prevent the art to be updated. Be careful its properties (x, y, ...) won't be able to change! +- Add physics flags to prevent running contact if not necessary (beginContactCallEnabled, endContactCallEnabled, etc.). +- Now physics is added to objects only when they are added to a state class. It's called addPhysics function. +- ACitrusView.update has the delta time in argument (and so its children). +- Instead of a simple parallax property, now there are two: parallaxX and parallaxY +- SoundManager can handles more than 32 sounds. +- StarlingArt handles Texture view. It creates an Image. +- AnimationSequence can add new animations and remove them. +- Added a FluidBox into complex objects using ThresholdFilter, metaballs effect. +- Removed set velocity on Box2D and Nape dynamic objects since we already use a reference. +- Nape MovingPlatform's default speed is 30. +- Improved Box2D Hills. +- Fixed a bug on Nape Missile's angle. +- Prevent to add several time the same object to the state. +- Added a PolarPoint math class. +- Added an Accelerometer Input Controller. +- Added a ScreenTouch Input Controller for Starling. + +V 3.1.4, 02 27 2013 +------------------- +- Renamed AVirtualButtons and VirtualButtons classes into AVirtualButton and VirtualButton. Yes, they just add one button now. Easier to add many ;) +- Added Starling's simple trick to avoid the state changes (alpha 0.999). +- StarlingCitrusEngine and Away3DCitrusEngine accepts State, useful to display quickly a state with graphics from a swf, ect. +- States classes have a new method killAllObjects(...except). The _objects variable has also a getter. +- States classes have a protected variable _ce which refers to the Citrus Engine. +- Nape's Hero has a static friction removed when the player move, and set when it stops moving (to prevent sliding). +- ObjectMakerStarling has a FromMovieClip function. The second argument is the TextureAtlas. Objects made in Flash Pro can use a texture name for their view. +- Added Panning to SoundManager +- Added a camera lens parameter to view.camera.setUp function. +- Added zoomFit() to StarlingCamera and SpriteCamera +- Added a get function command for the console +- Added a trace to inform if we create a group with a high value +- view.camera.setUp returns the instance of the ACitrusCamera. +- StarlingArt doesn't generate mipmaps if view is a Bitmap. +- Added Crate object to Nape. +- Added a UI package for inventory and lifebar. +- Added a Path class which is a set of points (MathVector) that can be used with the MovingPlatform. +- Added Nape version of the Moving Platform managing also a Path if it's specified. +- Added line equation to MathUtils. +- Added linear interpolation function to MathUtils. +- Added Tools class with a print_r function to display objects and arrays content. +- Improved the Timer's cannon: it is paused if the CE is not playing. +- Added a Bridge, Rope and Pool objects, into the new objects.complex.box2dstarling package. +- Added a Multiply2 function into Box2DUtils. + +V 3.1.3, 01 24 2013 +------------------- +- new Camera system ready! you don't call anymore view.setupCamera function, now it is view.camera.setUp +- input uses its own update loop using Event.FRAME_CONSTRUCTED. +- fixed tiled map parser's problem where the layer index might be wrong. +- fixed a bug in Keyboard's input where some actions weren't performed. +- fixed Starling VirtualButtons and VirtualJoystick 's destroy method. +- fixed a problem with parallax when zooming. +- fixed a bug where Nape Missile's angle wasn't in radian. +- Nape's Hero no longer has a static friction. +- fixed Nape's Hero was able if the collisionAngle was really close to 0. +- moved SpriteDebugArt and StarlingSpriteDebugArt into their respective package. +- SpriteArt/StarlingArt/Away3DArt content property becomes private with a getter. It should only be set internally. + +V 3.1.2, 01 17 2013 +------------------- +- improved physics performance removing the update call to the debug view if it isn't visible. +- outsourced camera stuff into a ACitrusCamera class and one camera by view : BlittingCamera, SpriteCamera, StarlingCamera and Away3DCamera2D. +- renamed CitrusView into ACitrusView class. +- addSound method has now two arguments : the id (String) and the sound (*, String or Class). +- added CitrusGroup class to group different kind of objects. +- added createAABB method in MathUtils package. +- added CollisionGetObjectByType into Box2DUtils and NapeUtils. +- added getObjectsByName method. +- added a fla with Citrus objects components to create quickly objects using Flash Pro as a level editor. +- you can change physics step thanks to their public var. +- updated on Starling 1.3. +- fixed a bug where the group property wasn't updated using Away3DView, SpriteView and StarlingView. +- fixed on StarlingArt, the object's view changed but animation doesn't update on the new view. + +V 3.1.1, 12 20 2012 +------------------- +- created starling and away3d package in citrus.core for StarlingCitrusEngine, StarlingState, Away3DCitrusEngine and Away3DState classes. +- removed stage argument in setUpStarling function, override the handleAddedToStage method to call setUpStarling function instead. +- added Nape parser for polygon/polyline. +- AVirtualJoystick : action value scaling. +- TimeShifter now listens to and routes input to his defaultChannel which remains channel 16 when instanciated. +- fixed a bug where Starling couldn't dispose. +- fixed a bug on TimeShifter using params. +- fixed camera offset for BlittingView. + +V 3.1.0, 12 14 2012 +------------------- +- Renamed package "com" and "citrusengine" into "citrus". +- The LevelManager can load levels made with Flash Pro on iOS using a LoaderContext. +- The setUpStarling function may take the flash stage as an argument (useful if not the root class). +- AnimationSequence's dictionary is now accessible thanks to a getter. +- Changed _input to protected to allow custom Input. +- Added the new input package supporting keyboard, joystick, button, channel, key action... +- Added a TimeShifter à la Braid! Allow also to replay an action. +- Upgraded on Nape 2.0. +- Nape's gravity is equal to Box2D's gravity. +- Nape's object physics behavior are closed to Box2d one (friction, speed, hero & enemy interaction...) +- refreshPoolObjectArt handles the startIndex. +- Now we can easily read the velocity of a body thanks to a getter. +- Thanks to ObjectMaker we can define vertices using Tiled Map Editor software. +- Update on Starling RC 1.3 + added its new AssetManager class. +- StarlingArt is now able to dispose automatically basic DisplayObject. +- Starling's AnimationSequence has a clone method. +- Starling's AnimationSequence dispatch onAnimationComplete Signal + +V 3.0.4, 11 29 2012 +------------------- +- DragonBones support for StarlingArt class. +- Moved ObjectMaker2D & 3D classes and tmx package into a new objectmakers package. +- Create a new ObjectMakerStarling class with a parser for Tiled Map Editor’s files optimized for Starling. + +V 3.0.3, 11 28 2012 +------------------- +- optimized MathVector class. +- a PoolObject can be rendered through the State/StarlingState classes. +- the LevelManager class can load tmx file. +- ATF file format are supported by the StarlingTileSystem class. +- tiled map objectmaker uses dynamic tileset name. +- ObjectMaker FromTiledMap support now multipe tileSets. +- added a RevolvingPlatform in box2d platformer’s package. +- the Starling’s AnimationSequence class has a new parameter : the smoothing. Default is bilinear. + +V 3.0.2, 11 20 2012 +------------------- +- fixed a bug where the MovingPlatform speed parameters wasn’t applied. +- fixed a critical bug using Android where the state could be instantiated before the context3D is created. +- fixed a bug if a flash display object was added using StarlingState, there were a problem destroying the state due to the physics debug view. +- the entity/component system works fine now with Box2D pure AS3 and enable interaction with “normal” object. +- all Box2D objects implements the IBox2DPhysicsObject interface. +- CollisionGetOther CollisionGetSelf Rotateb2Vec2 functions have moved into a new class : Box2DUtils +- Box2DShapeMaker class moved into physics/box2d package + +V 3.0.1, 11 16 2012 +------------------- +- fixed a bug on Nape’s Hero colliding with a Sensor. +- fixed a bug where the Box2D Hero was able to double jump thanks to a Sensor or a Cloud. +- fixed a bug where a passenger falls on a Moving Platform if it changes direction to downward. +- optimizing States loops. +- an Enemy doesn’t move anymore during the period it is hurt (don’t forget it is killed after this period). +- Hero switches facing direction while in mid-air. +- DistanceBetweenTwoPoints method added in MathUtils. +- CitrusEngine’s handleAddedToStage method is now protected. +- Adding auto setting of a component’s entity when adding to an entity, also added 2 utilites that search for a component by class type. +- Frame Rate Independent Motion support added! + +V 3.0.0, 11 6 2012 +------------------ +- Moved from Box2D Alchemy to Box2D pure AS3 for better performance +- Starling support +- Nape physics engine supported with some pre-built platformer objects +- Level Manager and an abstract game data class +- Object Pooling and Entity/Component System +- Away3D support +- AwayPhysics support +- New Level Editor handlers for: Tiled Map and Cadet 3D. +- Lots of examples with assets +- A new forum on Starling’s website +- Other cool features and performance improvement diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..f25876ef --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,27 @@ +Citrus Engine - Flash ActionScript3 Game Engine +----------------------------------------------- + +Authors: Eric Smith, Aymeric Lamboley, Thomas Lefevre. + +Copyright (c) Eric Smith, Aymeric Lamboley, Thomas Lefevre and contributors 2011 - 2016 +http://citrusengine.com/ + +Licence Agreement (The MIT License) + +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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..6cbfdbf8 --- /dev/null +++ b/README.md @@ -0,0 +1,75 @@ +**We recently reset this repository for working on a new version, at the moment we're making experiments. If you are looking for the old repository, find it [here](https://github.com/alamboley/Citrus-Engine).** + +[Get the examples here](https://github.com/alamboley/Citrus-Engine-Examples) + + +![](http://aymericlamboley.fr/blog/wp-content/uploads/2012/11/citrus-logo-2D.png) + +The [Citrus Engine](http://citrusengine.com/) is a professional-grade, scalable Flash game engine built for industry-quality games. It is built upon modern Flash programming practices, allowing you to focus on making your game awesome! It comes built-in with a "platformer" starter-kit, which you can use to easily make awesome 2D or 3D sidescrolling games. + +The [Citrus Engine](http://citrusengine.com/) is not only made for platformer games, but for all type of games. It offers a nice way to separate logic/physics from art. + +It offers many options, you may use : +- select between : the classic flash display list, blitting, [Starling](http://gamua.com/starling/) (including [Feathers](http://feathersui.com/)) and [Away3D](http://away3d.com/). +- select between : [Box2D](http://www.box2d.org/manual.html), [Nape](http://napephys.com/), [AwayPhysics](https://github.com/away3d/awayphysics-core-fp11) and a Simple math based collision detection. +- a simple way to manage object creation, and for advanced developers : an entity/component system and object pooling. +- a LevelManager and a LoadManager which may use Flash Pro as a level editor. +- a Console, Sound management class, Keyboard and input handler... + +Games References +---------------- +[![Mission Eau!](http://aymericlamboley.fr/blog/wp-content/uploads/2014/12/mission_eau.png)](http://www.aymericlamboley.fr/blog/mission-eau-being-agile-with-flash-pro/) +[![Chickdoom!](http://aymericlamboley.fr/blog/wp-content/uploads/2014/11/Chickdoom.png)](https://itunes.apple.com/us/app/chickdoom-chicken-vs-aliens/id860555162?mt=8) +[![FRNZY](http://aymericlamboley.fr/blog/wp-content/uploads/2014/11/FRNZY.png)](http://www.carefirstsocial.com/frnzy/) +[![Penguin Up!](http://aymericlamboley.fr/blog/wp-content/uploads/2014/11/Penguin-Up!.png)](https://itunes.apple.com/sg/app/penguin-up!/id900800765?mt=8) +[![Silly Family](http://aymericlamboley.fr/blog/wp-content/uploads/2014/04/silly-family.png)](http://rdrct.it/sillyfamily) +[![Paint Over](http://aymericlamboley.fr/blog/wp-content/uploads/2014/04/paintover.png)](http://www.newgrounds.com/portal/view/637713) +[![Revenge of Robots](http://aymericlamboley.fr/blog/wp-content/uploads/2014/04/revengeofrobots.png)](https://itunes.apple.com/us/app/revenge-of-robots/id855173133) +[![Tap The Pixel](http://aymericlamboley.fr/blog/wp-content/uploads/2014/03/tappixel.png)](https://itunes.apple.com/en/app/tap-the-pixel/id688562779?mt=8) +[![Once Upon a Light](http://aymericlamboley.fr/blog/wp-content/uploads/2013/12/once-upon.png)](http://www.onceuponalight.com) +[![Battlewars](http://aymericlamboley.fr/blog/wp-content/uploads/2013/11/Battlewars.png)](http://www.newgrounds.com/portal/view/627927) +[![Snailboy](http://aymericlamboley.fr/blog/wp-content/uploads/2013/09/snailboy.png)](http://www.thoopid.com/snailboy-presskit) +[![Monsieur Bear's ABC](http://aymericlamboley.fr/blog/wp-content/uploads/2013/09/abc.png)](http://rdrct.it/monsieurbearsabc) +[![Underwater Adventures](http://aymericlamboley.fr/blog/wp-content/uploads/2013/08/underwaterAdventures.png)](http://underwateradventuresgame.com/) +[![Roundhouse](http://aymericlamboley.fr/blog/wp-content/uploads/2013/06/roundhouse.png)](http://roundhousegame.com/) +[![Red n Green 2](http://aymericlamboley.fr/blog/wp-content/uploads/2013/05/red-green-2.png)](http://meowbeast.com/game/red-n-green-2/) +[![Sophie la Girafe](http://aymericlamboley.fr/blog/wp-content/uploads/2013/05/sophie-2.png)](https://itunes.apple.com/fr/app/sophie-la-girafe/id649739520?l=fr&ls=1&mt=8) +[![Beekyr](http://aymericlamboley.fr/blog/wp-content/uploads/2013/05/Beekyr.png)](https://play.google.com/store/apps/details?id=air.air.BeekyrAndroid) +[![Alef](http://aymericlamboley.fr/blog/wp-content/uploads/2013/04/Alef.png)](https://itunes.apple.com/us/app/alef/id632002337?ls=1&mt=8) +[![Jim loves Mary](http://aymericlamboley.fr/blog/wp-content/uploads/2013/04/jim-mary.png)](http://meowbeast.com/game/jim-loves-mary/) +[![Chorizos de España](http://aymericlamboley.fr/blog/wp-content/uploads/2013/03/Chorizos.png)](https://play.google.com/store/apps/details?id=air.com.ravalmatic.ChorizosDeEspana) +[![Shotgun vs Zombies](http://aymericlamboley.fr/blog/wp-content/uploads/2013/03/Shotgun-vs-Zombies.png)](http://armorgames.com/play/14737/shotgun-vs-zombies) +[![Gérard](http://aymericlamboley.fr/blog/wp-content/uploads/2013/02/gerard.png)](https://play.google.com/store/apps/details?id=air.com.studio3wg.gerard) +[![Santa Rush](http://aymericlamboley.fr/blog/wp-content/uploads/2012/12/Santa Rush.png)](https://play.google.com/store/apps/details?id=air.com.studio3wg.SantaRush) +[![Kepher](http://aymericlamboley.fr/blog/wp-content/uploads/2012/12/Kepher.png)](http://www.daarboven.net/kepher_comingsoon.html) +[![Stack of Defence](http://aymericlamboley.fr/blog/wp-content/uploads/2012/11/stackofdefence.png)](http://www.newgrounds.com/portal/view/606457) +[![Music Game](http://aymericlamboley.fr/blog/wp-content/uploads/2012/11/cynic.png)](http://cynicmusic.com/citrus/) +[![Les aventures d'Aïcha - l'odyssée des 4 mondes](http://aymericlamboley.fr/blog/wp-content/uploads/2012/11/Aicha.png)](https://www.facebook.com/aichaetvous/app_449473045088858) +[![Escape From Nerd Factory](http://aymericlamboley.fr/blog/wp-content/uploads/2012/09/escape-from-nerd-factory.jpg)](http://www.newgrounds.com/portal/view/598677) +[![Kinessia](http://aymericlamboley.fr/blog/wp-content/uploads/2012/08/Kinessia.jpg)](http://kinessia.aymericlamboley.fr/) +[![MarcoPoloWeltrennen](http://aymericlamboley.fr/blog/wp-content/uploads/2012/08/MarcoPoloWeltrennen.png)](http://www.marcopoloweltrennen.de/) +[![Tibi](http://aymericlamboley.fr/blog/wp-content/uploads/2012/09/Tibi.png)](http://hellorepublic.com/client/tibi/platform/) + +Repository Setup +---------------- +- ant : an Ant file to build the different swcs. +- bin : CE's swcs produced with all libraries included. +- ce-asdoc : used to generate the documentation. +- fla : a Components.fla file including several classes for box2d and nape objects ready to import in your own fla to make quickly a level with Flash Pro. +- lib : the different swcs used. +- src : Citrus Engine's source code. +- srclib : as3 files of some libraries. + +[Wiki](http://wiki.starling-framework.org/citrus/start) & [API](http://citrusengine.com/api/) + +Support +------- +The [Citrus Engine](http://citrusengine.com/) is proudly supported by: +[![FDT](http://aymericlamboley.fr/blog/wp-content/uploads/2013/12/fdt.png)](http://fdt.powerflasher.com/)[![Jetbrains](http://aymericlamboley.fr/blog/wp-content/uploads/2013/12/jetbrains.png)](http://www.jetbrains.com/) +If you have any questions on the engine or need some help, we suggest to ask directly on the [forum](http://forum.starling-framework.org/forum/citrus-engine). However for people and companies desiring a strong support, a freelance game developer, or special features you may contact us directly: citrusengine[dot]framework[at]gmail[dot]com +We will make a pricing plan depending on your need. + +Donations +--------- +The [Citrus Engine](http://citrusengine.com/) requested lots of work to become the most advanced Open-Source & free AS3 game engine. You can show us your love making a donation. It will be divided fairly between contributors. +[Donate](http://citrusengine.com/support/) diff --git a/ant/README.md b/ant/README.md new file mode 100644 index 00000000..d6d6fc90 --- /dev/null +++ b/ant/README.md @@ -0,0 +1,20 @@ +Build instructions +------------------ + +The ant is configured to be built on a PC or a Mac. + +*The instructions below aren't useful anymore. The ant manages everything now.* + +Before running the script, you must generate the comments for the ASDoc using [ASDocr](http://gskinner.com/blog/archives/2010/05/asdocr_update_f_1.html). + +Take a look on the screenshot to know how to configure it: + + +![ScreenShot](http://aymericlamboley.fr/blog/wp-content/uploads/2013/06/Capture0.PNG) +![ScreenShot](http://aymericlamboley.fr/blog/wp-content/uploads/2013/06/Capture1.PNG) +![ScreenShot](http://aymericlamboley.fr/blog/wp-content/uploads/2013/06/Capture2.PNG) +![ScreenShot](http://aymericlamboley.fr/blog/wp-content/uploads/2013/06/Capture3.PNG) + +Now you can run it! When it's done, you will be enable to run the ant script. + +If you want to generate the ASDoc in a html format, remove the additional params from the last screenshot picture. diff --git a/ant/build.properties b/ant/build.properties new file mode 100644 index 00000000..6fc2af45 --- /dev/null +++ b/ant/build.properties @@ -0,0 +1,5 @@ +#Change this to the installation directory of FLEX +FLEX_HOME_WINDOWS = C:/Users/Aymeric/Documents/SDKs/flex_sdk_4.6 +FLEX_HOME_UNIX = /Users/Aymeric/Documents/Workspace/SDKs/flex_sdk_4.6 + +VERSION = V3-1-12 \ No newline at end of file diff --git a/ant/build.xml b/ant/build.xml new file mode 100644 index 00000000..86994a9f --- /dev/null +++ b/ant/build.xml @@ -0,0 +1,417 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Generating Citrus Engine Global + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Generating Citrus Engine Core + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Generating Citrus Engine Box2D + + + + + + + + + + + + + + + + + + + + + + + + + Generating Citrus Engine Nape + + + + + + + + + + + + + + + + + + + + + + Generating Citrus Engine Starling + + + + + + + + + + + + + + + + + + + + + + + + + + + + Generating Citrus Engine Starling - Box2D + + + + + + + + + + + + + + + + Generating Citrus Engine Starling - Nape + + + + + + + + + + + + + + Generating Citrus Engine Away3D + + + + + + + + + + + + + + + + + + + + + Generating Citrus Engine Away3D - Box2D + + + + + + + + + + + + + + Generating Citrus Engine Away3D - Nape + + + + + + + + + + + + + + Generating Citrus Engine Away3D - AwayPhysics + + + + + + + + + + + + + + + + + + + + + + + + + + Generating Box2D SWC + + + + + + + + + + + + Generating Starling + Feathers + DragonBones SWC + + + + + + + + + + + + + + + Generating Away3D + AwayPhysics SWC + + + + + + + + + + + + diff --git a/ce-asdoc/README.md b/ce-asdoc/README.md new file mode 100644 index 00000000..5c0e7bba --- /dev/null +++ b/ce-asdoc/README.md @@ -0,0 +1 @@ +This is the repository where the SWCs/HTML comments will be generated. diff --git a/fla/Components.fla b/fla/Components.fla new file mode 100644 index 0000000000000000000000000000000000000000..a6eac1cbd38866189a027042aa90bbeff009b69b GIT binary patch literal 108916 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 B4#)ri literal 0 HcmV?d00001 diff --git a/fla/README.md b/fla/README.md new file mode 100644 index 00000000..8304b0e1 --- /dev/null +++ b/fla/README.md @@ -0,0 +1,10 @@ +Components.fla file contains several Citrus Object using Box2D and Nape ready to copy/paste in your own fla to create quickly a level via Flash Pro. The objects are defined as components. + +Be careful, the className property (with package + class name) is specified into MovieClip's code. + +Don't forget that you can always defined objects thanks to some code inside your MovieClip, for example: +
var className = "citrus.objects.platformer.box2d.Enemy";
+var params = {
+	view: "characters/enemy.swf",
+	leftBound: -300
+}
diff --git a/lib/EazeTween.swc b/lib/EazeTween.swc new file mode 100644 index 0000000000000000000000000000000000000000..83bbe8f58e75ec8cc265719a620722f3978346f8 GIT binary patch literal 27715 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 I_}A0_0rQvl$p8QV literal 0 HcmV?d00001 diff --git a/lib/feathers.swc b/lib/feathers.swc new file mode 100644 index 0000000000000000000000000000000000000000..ff5d82f0e80bf0bd4e161550c1cc438391f7e6c5 GIT binary patch literal 807001 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ⅈk{|zyJHixdY&P3s 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ⅈ2faNBR-1H(2h1- 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 TC(^wT{Lc^mxqWD3@c;i0+&w8I literal 0 HcmV?d00001 diff --git a/lib/nape-release.swc b/lib/nape-release.swc new file mode 100644 index 0000000000000000000000000000000000000000..2f892f9d7b90e76ffb6e3c080587328365cb3737 GIT binary patch literal 323060 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�?kr8A}G5Sq1LH(Q-0xYSsr{i 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

    } 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�p%eiS@k~aB*`->0^mNo;IiE%dL16#5> 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 z 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�tGzy%+l+KNt_$dC%0T-Oc57?YK*!d2x-6Gg zX4G7CT&ynp*MvB;R8^vMU@k7V-AG(mN~%%0$R*dPURp-itXy1B>r^i;uXk{&y-qFi zJi5pg-e}}`WjM=;zA|6tpl&zHtD&9e40o7?-)K~LrJV1ebaW`Hsh#Jno?l$LJmYUa zg6a4a*5DRYhR&#Qr`ZDRkIrz1gHc+=fou~>HEY~AWT9*o5s1qi4LP`>b zK*uB+KVag`uK|0d3Qt%QBD9vcn*L+7pB>n2Y#7D2dvR#fVP3%B@Cdd8 zQusGFV(HLW3f5!r5D?GC!u_L!IY3_;qNh5N#kX8~72Kda{D{SY-Iz$Cx9vXt=|LC*@yX^9;s( z+jMgCr)^I`Rp1_1O7}EX&5!jVD2=~f(EE@_suGJA`W%EiQ*|te#0KDV2*e%H#q!l9 z`#jEx<&}EA#Fa`ijfJU9YeGyxm5atO8@RU9x;0BpfPvgxCxQhPvTX<>X1J6UFVvCL z7ZQ}_4P3b;dep+*5V2PhIU>Eo#-a`DLD9jDJUlBIsNB!woRASXd>J+Mf5FXRmP&#h zMmeB|E+>6SErTVkNMK1a#pGBcD zrOpB$#JwBw$OMSFaGvt8ScG3m@Fc_k7!X|&RK$Okm;p+1%k+cS^aD5Z{88A0OeX2i zN%)TV1+n<;k84jn+gy%0ISB)wAxyD50jXJZ7C%7-bp8u@cY%q7U@X2ip+X7yklAeU z(*w=9h12=bSkEb!Hn$6_inM_92~uP%v$~F_-03+{8tUXdFlC^1M2jE@cn z>Y6GXI&shk*Kuc>vy_u*QES}g)x(h|7>x#-ZR#S+!e)Clm)4*5#|B{|C0o=@8mK7F z18Z<7=c7A|2R4Orioi`{O;W#Ed|UABtVxpFJAPOrj)i{2t6v9(EiU$jTxfeKW3&y$ z_Z|})oOO7{@vURTR#|P*YdN*z(~nfD%hCso*kNPk$nmv?)*~kP-tLsWYlHqxrP+p8 zY#p09pGRkxo*LE*%fuQ}=VlEx0(ZLk<>HPG>D9Yd!By4s3KGXkyE&GyE%3&rZ&Do| z=~dX(=H(98pqlFWW+E4x>YC<@4TTQZrka|?=I0LAs2beL%F*);FdeJK<>(IAt{U5g z<>?OBfSMi^HMynbMCWH4XcX&a$e}9@NsGT}FdXfm(#AQ8noa8I(d=P{KneC1Bu2&P zkzX{}R8vbyd-4>Wz3()PXh@SoX^3JuB8(*3re%p(!x8IakLqp|G0t+u?UphmG;ti| zgz@r{$rj;sD}X-z*kSK+Ydnv^2f=kWc7UfLb+|$L%vyhka6^W0^Sgmi<%5=#3Vn_S zWC@^3a`16;P03mL4QF|!~C7W$8i#F=-Y{m-p|~5B~cfUjNnH{ zpcy!6BHTc~>chjmMR+yC4sZs!WDhc|(2(FS3cG;IiW=3Di%zSiJ~kKMoy#rdgvuL~ z+ugYJbSj7~m@gxBCN+n>r`0d4k^j0U(2xM73wYhFjKXxwN7F}$!12@9L{F1wEFX?! z0HlOA8L=kh6J#wLHA1n3_(_@Q(zv%MWBvq0oORsadw=@`dyXtwxa~1LXCxP`7IACT zYNUIDyF&18Xe8UsbN8k9h*+W9S*10IJW6jDcucmK!x)Jxw1_;mwVLC8yraFMMo-f0 z7I++?PA}eIP`Q`b%wNo{y=XE&l5b4u`{RE8I{2GIqivmnEwQLGS@UzGye8xB_*VhwV$CrMZ}@v=!H^rwgE{A4J$$@)+WP;=?Erk1-t-TthAG3|e<87vatFq& zXU#Kh0|$;VC~+)NuJ6(|#%N~E%Ub$3TGts^bKjaDEya$XN7=U1tYD9UEl(NdZKGU& z$A85DSd)s%-Y!2!U1)mgpS^IOe`-psc`lzD{u87QRxgglB{1lA{5ZrF7eQDC-Y5A5 zma9t5Y>MTL4Z2IwXMv>OJ8O&;?mZ>Uk#aY^9#Ji1Q`$DN$!otiFBbpg8C00C)WqwNQBU?%>41AUa*W~*b80`$R>{yS%jz+e! ziU%PxbBY`*BVX!Py~o2DYr3&0aDzDn2jc)f1c@lM=}otib3Ku|DAx}9Bb$ZpqMM7i zRhoLm$O~lxk=@;zt=^Lz7Mqm_yFIOHU@uamE-$XO(=h6vl|slnZb`RE2SG4fuv{tw z>tvZ)wT68$OATO0-vvo-)z=&?P=h-)GySr^_GWTy*c>}~>s+oiDQB=sv`yvX&nIA4 z-4JBwXAFU*R{-sM(=n!hE9&YSz52Ek&NK?~dJ3$F2sncg>KvI+c4vCxUFDN+d{VQG ztSxKLH;*<0Us5htt|iBOP_D-aOWj`G*oz4zX7R({`(t1`rOHYK()%ITxQQn!1q94t zyCcFM4#=tFM#0Jto3u|Un*5kwMGzFd*NR8VVND43t@UcJ!xfsi`gS>&GI6eME>*n1 zY-~csy%u==n6nji!5vF=!o(JXYoL2+fjS^A0#tAa3%u^D=6D=*h@I^eUsW!0nrJJW z6{;gKb(Ss+Q)n-o6>1~bJL0h7+dI<4un_9`(PTQt&I&ZpXgbu(yxzG0SV;odNDSL) zRnH8IX;sgGSUkFEVHbw6obN?-u(9C@OOhz9&$%%bBpKi%6fRUj5 zXNW-xjjbdphNT>;0Fn>ioe{A@8Q8;v`!nTYkSzVVlNXqDBW{cB(sB|)R~ngD8YjZN zRkWHJ>q&BIDL`6>$e>up(q=%WH@ca8QP7=*%45{DWS*g)y^|}B)`gbu=YbVfl~nNx zLDG>MEobMoxZg3mG#p>6#N6DEUS&WA+lQHQ;7~35PPl{ded?yzQZ-*m-6+aZ-f$Q0B>GIpufsYwhI>hCH@y7tR=QdEGg}j1ldI_`c!v!DNMFKRGI{#Fc z)2GG=r`dAC)lWIok-g05os{EcO3Hj;WHw08kv*B(B604Ak4v1qDEm1hgv+r|GhW7& zKr>El_IV5%&mq{BW}tOxDPMpk&suoyme;5y2c%wUZE}Phl!GpzJ(UnI;T(xdJW5hQ zQEd(yRav|up~WDg={9>$QXUMrO!@Zy0N2yKOj1X_*O(#xQs#7Bhx)xz#8>vZUvB=~ z1@2~B^pK(aynYTIeNsGDlS{2EeSeICFXN(~`rR@O`u(E3`@Ya)_=N@gy=^znzCYi) z={c^E`6LXhNvk+tb`XkyWl@!qzzxm_f(ddB>puU#4Ee$cQhfkSV z)!1de4r#jl<&k8y0DInpn1wD+JY3 zyR19Qs7qS{gm!dJ371r~jy~Olva+YOKAr{_dhb`cKH9knG~t?O?^O+Q_`SB+WJ;Kv zoe?3aU#+C)mk27dsJ?Zyx}Zkx>}$`u>ZzT!{9GSl8=Qt&RxNRQwG#I2Zb#?fUfn$X zsak_YzyDJ;2}2B718dz&S})zI}psqPM@UY)mpiZ(&P4AGh@2E zgRt8Pwtt7*K(%}&`48@e&j;DV9VBH{-}PB!126Ub?RS1Bv%n&-^2;_QspzThgib8; zJDCOibZFDBC}EPsP>}JeFFEcol+9j6CnKN~zh{JlJK%ml$RmkSR+pFtImTmtxsNv> zm8sD};wr|gA}DhO?=+K-<}!4uXD`NTiiN>QKZ- zAp{@!?hb_rzTjVw8$Cf%&8@SI9$BXK^qQwcB~`2CJ%F$*FnvPfjO4I$C}O+(I8dTJ z*9bI+DnJEaATAAWcPQ81weSJv(Mi71#BQ`5+&C;F99QBG+vEiAY!o~3

    97q6?C~ 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

    5EEL~LiEWM=^oV7_jV=mN{S0mvmfF~}+r!XKuI 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 z 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%0DvZRi>a-8nl#La>M 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 is a singleton so that you can grab a reference to it anywhere, anytime. Don't abuse this power, + * but use it wisely. With it, you can quickly grab a reference to the manager classes such as current State, Input and SoundManager.

    + */ + public class CitrusEngine extends MovieClip + { + public static const VERSION:String = "3.1.12"; + + private static var _instance:CitrusEngine; + + /** + * DEBUG is not used by CitrusEngine, it is there for your own convenience + * so you can access it wherever the _ce 'shortcut' is. defaults to false. + */ + public var DEBUG:Boolean = false; + + /** + * Used to pause animations in SpriteArt and StarlingArt. + */ + public var onPlayingChange:Signal; + + /** + * called after a stage resize event + * signal passes the new screenWidth and screenHeight as arguments. + */ + public var onStageResize:Signal; + + /** + * You may use a class to store your game's data, this is already an abstract class made for that. + * It's also a dynamic class, so you won't have problem to access information in its extended class. + */ + public var gameData:AGameData; + + /** + * You may use the Citrus Engine's level manager if you have several levels to handle. Take a look on its class for more information. + */ + public var levelManager:LevelManager; + + /** + * the matrix that describes the transformation required to go from state container space to flash stage space. + * note : this does not include the camera's transformation. + * the transformation required to go from flash stage to in state space when a camera is active would be obtained with + * var m:Matrix = camera.transformMatrix.clone(); + * m.concat(_ce.transformMatrix); + * + * using flash only, the state container is aligned and of the same scale as the flash stage, so this is not required. + */ + public const transformMatrix:Matrix = new Matrix(); + + protected var _state:IState; + protected var _newState:IState; + protected var _stateTransitionning:IState; + protected var _futureState:IState; + protected var _stateDisplayIndex:uint = 0; + protected var _playing:Boolean = true; + protected var _input:Input; + + protected var _fullScreen:Boolean = false; + protected var _screenWidth:int = 0; + protected var _screenHeight:int = 0; + + private var _startTime:Number; + private var _gameTime:Number; + private var _nowTime:Number; + protected var _timeDelta:Number; + + private var _sound:SoundManager; + private var _console:Console; + + public static function getInstance():CitrusEngine + { + return _instance; + } + + /** + * Flash's innards should be calling this, because you should be extending your document class with it. + */ + public function CitrusEngine() + { + _instance = this; + + onPlayingChange = new Signal(Boolean); + onStageResize = new Signal(int, int); + + onPlayingChange.add(handlePlayingChange); + + // on iOS if the physical button is off, mute the sound + if ("audioPlaybackMode" in SoundMixer) + try { SoundMixer.audioPlaybackMode = "ambient"; } + catch(e:ArgumentError) { + trace("[CitrusEngine] could not set SoundMixer.audioPlaybackMode to ambient."); + } + + //Set up console + _console = new Console(9); //Opens with tab key by default + _console.onShowConsole.add(handleShowConsole); + _console.addCommand("set", handleConsoleSetCommand); + _console.addCommand("get", handleConsoleGetCommand); + addChild(_console); + + //timekeeping + _gameTime = _startTime = new Date().time; + + //Set up input + _input = new Input(); + + //Set up sound manager + _sound = SoundManager.getInstance(); + + addEventListener(Event.ENTER_FRAME, handleEnterFrame); + addEventListener(Event.ADDED_TO_STAGE, handleAddedToStage); + } + + /** + * Destroy the Citrus Engine, use it only if the Citrus Engine is just a part of your project and not your Main class. + */ + public function destroy():void { + + onPlayingChange.removeAll(); + onStageResize.removeAll(); + + stage.removeEventListener(Event.ACTIVATE, handleStageActivated); + stage.removeEventListener(Event.DEACTIVATE, handleStageDeactivated); + stage.removeEventListener(FullScreenEvent.FULL_SCREEN, handleStageFullscreen); + stage.removeEventListener(Event.RESIZE, handleStageResize); + + removeEventListener(Event.ENTER_FRAME, handleEnterFrame); + + if (_state) { + + _state.destroy(); + + if (_state is State) + removeChild(_state as State); + } + + _console.destroy(); + removeChild(_console); + + _input.destroy(); + _sound.destroy(); + } + + /** + * A reference to the active game state. Actually, that's not entirely true. If you've recently changed states and a tick + * hasn't occurred yet, then this will reference your new state; this is because actual state-changes only happen pre-tick. + * That way you don't end up changing states in the middle of a state's tick, effectively fucking stuff up. + * + * If you had set up a futureState, accessing the state it wil return you the futureState to enable some objects instantiation + * (physics, views, etc). + */ + public function get state():IState + { + if (_futureState) + return _futureState; + + else if (_newState) + return _newState; + + else + return _state; + } + + /** + * We only ACTUALLY change states on enter frame so that we don't risk changing states in the middle of a state update. + * However, if you use the state getter, it will grab the new one for you, so everything should work out just fine. + */ + public function set state(value:IState):void + { + _newState = value; + } + + /** + * Get a direct access to the futureState. Note that the futureState is really set up after an update so it isn't + * available via state getter before a state update. + */ + public function get futureState():IState { + return _futureState ? _futureState : _stateTransitionning; + } + + /** + * The futureState variable is useful if you want to have two states running at the same time for making a transition. + * Note that the futureState is added with the same index than the state, so it will be behind unless the state runs + * on Starling and the futureState on the display list (which is absolutely doable). + */ + public function set futureState(value:IState):void { + _stateTransitionning = value; + } + + /** + * @return true if the Citrus Engine is playing + */ + public function get playing():Boolean + { + return _playing; + } + + /** + * Runs and pauses the game loop. Assign this to false to pause the game and stop the + * update() methods from being called. + * Dispatch the Signal onPlayingChange with the value. + * CitrusEngine calls its own handlePlayingChange listener to + * 1.reset all input actions when "playing" changes + * 2.pause or resume all sounds. + * override handlePlayingChange to override all or any of these behaviors. + */ + public function set playing(value:Boolean):void + { + if (value == _playing) + return; + + _playing = value; + if (_playing) + _gameTime = new Date().time; + onPlayingChange.dispatch(_playing); + } + + /** + * You can get access to the Input manager object from this reference so that you can see which keys are pressed and stuff. + */ + public function get input():Input + { + return _input; + } + + /** + * A reference to the SoundManager instance. Use it if you want. + */ + public function get sound():SoundManager + { + return _sound; + } + + /** + * A reference to the console, so that you can add your own console commands. See the class documentation for more info. + * The console can be opened by pressing the tab key. + * There is one console command built-in by default, but you can add more by using the addCommand() method. + * + *

    To try it out, try using the "set" command to change a property on a CitrusObject. You can toggle Box2D's + * debug draw visibility like this "set Box2D visible false". If your Box2D CitrusObject instance is not named + * "Box2D", use the name you gave it instead.

    + */ + public function get console():Console + { + return _console; + } + + /** + * Set up things that need the stage access. + */ + protected function handleAddedToStage(e:Event):void + { + removeEventListener(Event.ADDED_TO_STAGE, handleAddedToStage); + stage.scaleMode = StageScaleMode.NO_SCALE; + stage.align = StageAlign.TOP_LEFT; + stage.addEventListener(Event.DEACTIVATE, handleStageDeactivated); + stage.addEventListener(Event.ACTIVATE, handleStageActivated); + + stage.addEventListener(FullScreenEvent.FULL_SCREEN, handleStageFullscreen); + stage.addEventListener(Event.RESIZE, handleStageResize); + + _fullScreen = (stage.displayState == StageDisplayState.FULL_SCREEN || stage.displayState == StageDisplayState.FULL_SCREEN_INTERACTIVE); + resetScreenSize(); + + _input.initialize(); + + this.initialize(); + } + + /** + * Called when CitrusEngine is added to the stage and ready to run. + */ + public function initialize():void { + } + + protected function handleStageFullscreen(e:FullScreenEvent):void + { + _fullScreen = e.fullScreen; + } + + protected function handleStageResize(e:Event):void + { + resetScreenSize(); + onStageResize.dispatch(_screenWidth, _screenHeight); + } + + /** + * on resize or fullscreen this is called and makes sure _screenWidth/_screenHeight is correct, + * it can be overriden to update other values that depend on the values of _screenWidth/_screenHeight. + */ + protected function resetScreenSize():void + { + _screenWidth = stage.stageWidth; + _screenHeight = stage.stageHeight; + } + + /** + * called when the value of 'playing' changes. + * resets input actions , pauses/resumes all sounds by default. + */ + protected function handlePlayingChange(value:Boolean):void + { + if(input) + input.resetActions(); + + if (sound) + if(value) + sound.resumeAll(); + else + sound.pauseAll(); + } + + /** + * This is the game loop. It switches states if necessary, then calls update on the current state. + */ + //TODO The CE updates use the timeDelta to keep consistent speed during slow framerates. However, Box2D becomes unstable when changing timestep. Why? + protected function handleEnterFrame(e:Event):void + { + //Change states if it has been requested + if (_newState && _newState is State) { + + if (_state && _state is State) { + + _state.destroy(); + removeChild(_state as State); + } + + _state = _newState; + _newState = null; + + if (_futureState) + _futureState = null; + + else { + addChildAt(_state as State, _stateDisplayIndex); + _state.initialize(); + } + + } + + if (_stateTransitionning && _stateTransitionning is State) { + + _futureState = _stateTransitionning; + _stateTransitionning = null; + + addChildAt(_futureState as State, _stateDisplayIndex); + _futureState.initialize(); + } + + //Update the state + if (_state && _playing) + { + _nowTime = new Date().time; + _timeDelta = (_nowTime - _gameTime) * 0.001; + _gameTime = _nowTime; + + _state.update(_timeDelta); + if (_futureState) + _futureState.update(_timeDelta); + } + + _input.citrus_internal::update(); + + } + + /** + * Set CitrusEngine's playing to false. Every update methods aren't anymore called. + */ + protected function handleStageDeactivated(e:Event):void + { + playing = false; + } + + /** + * Set CitrusEngine's playing to true. The main loop is performed. + */ + protected function handleStageActivated(e:Event):void + { + playing = true; + } + + private function handleShowConsole():void + { + if (_input.enabled) + { + _input.enabled = false; + _console.onHideConsole.addOnce(handleHideConsole); + } + } + + private function handleHideConsole():void + { + _input.enabled = true; + } + + private function handleConsoleSetCommand(objectName:String, paramName:String, paramValue:String):void + { + var object:CitrusObject = _state.getObjectByName(objectName); + + if (!object) + { + trace("Warning: There is no object named " + objectName); + return; + } + + var value:Object; + if (paramValue == "true") + value = true; + else if (paramValue == "false") + value = false; + else + value = paramValue; + + if (object.hasOwnProperty(paramName)) + object[paramName] = value; + else + trace("Warning: " + objectName + " has no parameter named " + paramName + "."); + } + + private function handleConsoleGetCommand(objectName:String, paramName:String):void + { + var object:CitrusObject = _state.getObjectByName(objectName); + + if (!object) + { + trace("Warning: There is no object named " + objectName); + return; + } + + if (object.hasOwnProperty(paramName)) + trace(objectName + " property:" + paramName + "=" + object[paramName]); + else + trace("Warning: " + objectName + " has no parameter named " + paramName + "."); + } + + public function get fullScreen():Boolean + { + return _fullScreen; + } + + public function set fullScreen(value:Boolean):void + { + if (value == _fullScreen) + return; + + if(value) + stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE; + else + stage.displayState = StageDisplayState.NORMAL; + + resetScreenSize(); + } + + public function get screenWidth():int + { + return _screenWidth; + } + + public function get screenHeight():int + { + return _screenHeight; + } + } +} \ No newline at end of file diff --git a/src/citrus/core/CitrusGroup.as b/src/citrus/core/CitrusGroup.as new file mode 100644 index 00000000..9fba067c --- /dev/null +++ b/src/citrus/core/CitrusGroup.as @@ -0,0 +1,144 @@ +package citrus.core { + + import citrus.system.Entity; + import citrus.view.ISpriteView; + + /** + * A CitrusGroup defines a group of objects which may be of different kind. It extends Entity class, it means that you can easily add components to + * a CitrusGroup to define different behaviors. You can also set quickly different params to all group's objects and their view. + */ + public class CitrusGroup extends Entity { + + protected var _groupObjects:Vector. = new Vector.(); + + public function CitrusGroup(name:String, params:Object = null) { + super(name, params); + } + + override public function destroy():void { + + _groupObjects.length = 0; + + super.destroy(); + } + + /** + * Add an object to the group. + * @param object An object to add to the group. + * @return return the CitrusGroup for chained operation. + */ + public function addObject(object:CitrusObject):CitrusGroup { + + _groupObjects.push(object); + + return this; + } + + /** + * Remove an object of the group. + * @param object An object to remove from the group. + * @return return the CitrusGroup for chained operation. + */ + public function removeObject(object:CitrusObject):CitrusGroup { + + _groupObjects.splice(_groupObjects.indexOf(object), 1); + + return this; + } + + /** + * Define properties for all objects into the group like we do for a CitrusObject. + * @param param An object where properties and value are defined. + */ + public function setParamsOnObjects(param:Object):void { + + for each (var object:CitrusObject in _groupObjects) + setParams(object, param); + } + + /** + * Define properties for all objects' view into the group like we do for a CitrusObject. + * @param param An object where properties and value are defined. + */ + public function setParamsOnViews(param:Object):void { + + for each (var object:ISpriteView in _groupObjects) + setParams(object.view, param); + } + + /** + * Gets a reference to a CitrusObject by passing that object's name in. + * Often the name property will be set via a level editor such as the Flash IDE. + * @param name The name property of the object you want to get a reference to. + */ + public function getObjectByName(name:String):CitrusObject { + + for each (var object:CitrusObject in _groupObjects) { + if (object.name == name) + return object; + } + + return null; + } + + /** + * This returns a vector of all objects of a particular name. This is useful for adding an event handler + * to objects that aren't similar but have the same name. For instance, you can track the collection of + * coins plus enemies that you've named exactly the same. Then you'd loop through the returned vector to change properties or whatever you want. + * @param name The name property of the object you want to get a reference to. + */ + public function getObjectsByName(name:String):Vector. { + + var objects:Vector. = new Vector.(); + + for each (var object:CitrusObject in _groupObjects) { + if (object.name == name) + objects.push(object); + } + + return objects; + } + + /** + * Returns the first instance of a CitrusObject that is of the class that you pass in. + * This is useful if you know that there is only one object of a certain time in your state (such as a "Hero"). + * @param type The class of the object you want to get a reference to. + */ + public function getFirstObjectByType(type:Class):CitrusObject { + + for each (var object:CitrusObject in _groupObjects) { + if (object is type) + return object; + } + + return null; + } + + /** + * This returns a vector of all objects of a particular type. This is useful for adding an event handler + * to all similar objects. For instance, if you want to track the collection of coins, you can get all objects + * of type "Coin" via this method. Then you'd loop through the returned array to add your listener to the coins' event. + * @param type The class of the object you want to get a reference to. + */ + public function getObjectsByType(type:Class):Vector. { + + var objects:Vector. = new Vector.(); + + for each (var object:CitrusObject in _groupObjects) { + if (object is type) { + objects.push(object); + } + } + + return objects; + } + + /** + * groupObjects is a vector containing all the objects registered into the group. + */ + public function get groupObjects():Vector. { + return _groupObjects; + } + + } +} diff --git a/src/citrus/core/CitrusObject.as b/src/citrus/core/CitrusObject.as new file mode 100644 index 00000000..256290b4 --- /dev/null +++ b/src/citrus/core/CitrusObject.as @@ -0,0 +1,151 @@ +package citrus.core +{ + /** + * CitrusObject is simple. Too simple. Despite its simplicity, it is the foundational object that should + * be used for all game objects logic you create, such as spaceships, enemies, coins, bosses. + * CitrusObject is basically an abstract class that gets added to a State instance. + * The current State calls update on all CitrusObjects. Also, CitrusObjects are useful because they can be + * initialized with a params object, which can be created via an object parser/factory. + */ + public class CitrusObject + { + /** + * data used internally + */ + citrus_internal var data:Object = {ID:0}; + citrus_internal static var last_id:uint = 0; + + public static var hideParamWarnings:Boolean = false; + + /** + * A name to identify easily an objet. You may use duplicate name if you wish. + */ + public var name:String; + + /** + * Set it to true if you want to remove, clean and destroy the object. + */ + public var kill:Boolean = false; + + /** + * This property prevent the update method to be called by the enter frame, it will save performances. + * Set it to true if you want to execute code in the update method. + */ + public var updateCallEnabled:Boolean = false; + + /** + * Added to the CE's render list via the State and the add method. + */ + public var type:String = "classicObject"; + + protected var _initialized:Boolean = false; + protected var _ce:CitrusEngine; + + protected var _params:Object; + + /** + * The time elasped between two update call. + */ + protected var _timeDelta:Number; + + /** + * Every Citrus Object needs a name. It helps if it's unique, but it won't blow up if it's not. + * Also, you can pass parameters into the constructor as well. Hopefully you'll commonly be + * creating CitrusObjects via an editor, which will parse your shit and create the params object for you. + * @param name Name your object. + * @param params Any public properties or setters can be assigned values via this object. + * + */ + public function CitrusObject(name:String, params:Object = null) + { + this.name = name; + + _ce = CitrusEngine.getInstance(); + + _params = params; + + if (params) { + if (type == "classicObject" && !params["type"]) + initialize(); + } else + initialize(); + + citrus_internal::data.ID = citrus_internal::last_id += 1; + } + + /** + * Call in the constructor if the Object is added via the State and the add method. + *

    If it's a pool object or an entity initialize it yourself.

    + *

    If it's a component, it should be call by the entity.

    + */ + public function initialize(poolObjectParams:Object = null):void { + + if (poolObjectParams) + _params = poolObjectParams; + + if (_params) + setParams(this, _params); + else + _initialized = true; + + } + + /** + * Seriously, dont' forget to release your listeners, signals, and physics objects here. Either that or don't ever destroy anything. + * Your choice. + */ + public function destroy():void + { + citrus_internal::data = null; + _initialized = false; + _params = null; + } + + /** + * The current state calls update every tick. This is where all your per-frame logic should go. Set velocities, + * determine animations, change properties, etc. + * @param timeDelta This is a ratio explaining the amount of time that passed in relation to the amount of time that + * was supposed to pass. Multiply your stuff by this value to keep your speeds consistent no matter the frame rate. + */ + public function update(timeDelta:Number):void + { + _timeDelta = timeDelta; + } + + /** + * The initialize method usually calls this. + */ + public function setParams(object:Object, params:Object):void + { + for (var param:String in params) + { + try + { + if (params[param] == "true") + object[param] = true; + else if (params[param] == "false") + object[param] = false; + else + object[param] = params[param]; + } + catch (e:Error) + { + if (!hideParamWarnings) + trace("Warning: The property " + param + " does not exist on " + this); + } + } + _initialized = true; + } + + public function get ID():uint + { + return citrus_internal::data.ID; + } + + public function toString():String + { + use namespace citrus_internal; + return String(Object(this).constructor) + " ID:" + (data && data["ID"] ? data.ID : "null") + " name:" + String(name) + " type:" + String(type); + } + } +} \ No newline at end of file diff --git a/src/citrus/core/Console.as b/src/citrus/core/Console.as new file mode 100644 index 00000000..9e6cee33 --- /dev/null +++ b/src/citrus/core/Console.as @@ -0,0 +1,339 @@ +package citrus.core { + + import org.osflash.signals.Signal; + + import flash.display.Sprite; + import flash.events.Event; + import flash.events.FocusEvent; + import flash.events.KeyboardEvent; + import flash.net.SharedObject; + import flash.text.TextField; + import flash.text.TextFieldType; + import flash.text.TextFormat; + import flash.ui.Keyboard; + import flash.utils.Dictionary; + + /** + * You can use the console to perform any type of command at your game's runtime. Press the key that opens it, then type a + * command into the console, then press enter. If your command is recognized, the command's handler function will fire. + * + *

    You can create your own console commands by using the addCommand method.

    + * + *

    When the console is open, it does not disable game input. You can manually toggle game input by listening for + * the onShowConsole and onHideConsole Signals.

    + * + *

    When the console is open, you can press the up key to step backwards through your executed command history, + * even after you've closed your SWF. Pressing the down key will step forward through your history. + * Use this to quickly access commonly executed commands.

    + * + *

    Each command follows this pattern: commandName param1 param2 param3.... First, you call the + * command name that you want to execute, then you pass any parameters into the command. For instance, you can + * set the jumpHeight property on a Hero object using the following command: "set myHero jumpHeight 20". That + * command finds an object named "myHero" and sets its jumpHeight property to 20.

    + * + *

    Make sure and see the addCommand definition to learn how to add your own console commands.

    + */ + public class Console extends Sprite + { + /** + * Default is tab key. + */ + public var openKey:uint = 9; + + private var _inputField:TextField; + private var _executeKey:int; + private var _prevHistoryKey:int; + private var _nextHistoryKey:int; + private var _commandHistory:Array; + private var _historyMax:Number; + private var _showing:Boolean; + private var _currHistoryIndex:int; + private var _numCommandsInHistory:Number; + private var _commandDelegates:Dictionary; + private var _shared:SharedObject; + private var _enabled:Boolean = false; + + //events + private var _onShowConsole:Signal; + private var _onHideConsole:Signal; + + /** + * Creates the instance of the console. This is a display object, so it is also added to the stage. + */ + public function Console(openKey:int = 9) + { + addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); + + _shared = SharedObject.getLocal("history"); + + this.openKey = openKey; + _executeKey = Keyboard.ENTER; + _prevHistoryKey = Keyboard.UP; + _nextHistoryKey = Keyboard.DOWN; + _historyMax = 25; + _showing = false; + _currHistoryIndex = 0; + _numCommandsInHistory = 0; + + if (_shared.data.history) + { + _commandHistory = _shared.data.history as Array; + _numCommandsInHistory = _commandHistory.length; + } + else + { + _commandHistory = new Array(); + _shared.data.history = _commandHistory; + } + _commandDelegates = new Dictionary(); + + _inputField = addChild(new TextField()) as TextField; + _inputField.type = TextFieldType.INPUT; + _inputField.addEventListener(FocusEvent.FOCUS_OUT, onConsoleFocusOut); + _inputField.defaultTextFormat = new TextFormat("_sans", 14, 0xFFFFFF, false, false, false); + + visible = false; + + _onShowConsole = new Signal(); + _onHideConsole = new Signal(); + } + + public function destroy():void { + + stage.removeEventListener(KeyboardEvent.KEY_UP, onToggleKeyPress); + + _onShowConsole.removeAll(); + _onHideConsole.removeAll(); + } + + /** + * Gets dispatched when the console is shown. Handler accepts 0 params. + */ + public function get onShowConsole():Signal + { + return _onShowConsole; + } + + /** + * Gets dispatched when the console is hidden. Handler accepts 0 params. + */ + public function get onHideConsole():Signal + { + return _onHideConsole; + } + + /** + * Determines whether the console can be used. Set this property to false before releasing your final game. + */ + public function get enabled():Boolean + { + return _enabled; + } + + public function set enabled(value:Boolean):void + { + if (_enabled == value) + return; + + _enabled = value; + + if (_enabled) + { + stage.addEventListener(KeyboardEvent.KEY_UP, onToggleKeyPress); + } + else + { + stage.removeEventListener(KeyboardEvent.KEY_UP, onToggleKeyPress); + hideConsole(); + } + } + + /** + * Can be called to clear the command history. + */ + public function clearStoredHistory():void + { + _shared.clear(); + } + + /** + * Adds a command to the console. Use this method to create your own commands. The name parameter + * is the word that you must type into the console to fire the command handler. The func parameter + * is the function that will fire when the console command is executed. + * + *

    Your command handler should accept the parameters that are expected to be passed into the command. All + * of them should be typed as a String. As an example, this is a valid handler definition for the "set" command.

    + * + *

    private function handleSetPropertyCommand(objectName:String, propertyName:String, propertyValue:String):void

    + * + *

    You can then create logic for your command using the arguments.

    + * + * @param name The word you want to use to execute your command in the console. + * @param func The handler function that will get called when the command is executed. This function should accept the commands parameters as arguments. + * + */ + public function addCommand(name:String, func:Function):void + { + _commandDelegates[name] = func; + } + + public function addCommandToHistory(command:String):void + { + var commandIndex:int = _commandHistory.indexOf(command); + if (commandIndex != -1) + { + _commandHistory.splice(commandIndex, 1); + _numCommandsInHistory--; + } + + _commandHistory.push(command); + _numCommandsInHistory++; + + if (_commandHistory.length > _historyMax) + { + _commandHistory.shift(); + _numCommandsInHistory--; + } + + _shared.flush(); + } + + public function getPreviousHistoryCommand():String + { + if (_currHistoryIndex > 0) + _currHistoryIndex--; + + return getCurrentCommand(); + } + + public function getNextHistoryCommand():String + { + if (_currHistoryIndex < _numCommandsInHistory) + _currHistoryIndex++; + + return getCurrentCommand(); + } + + public function getCurrentCommand():String + { + var command:String = _commandHistory[_currHistoryIndex]; + + if (!command) + { + return ""; + } + return command; + } + + public function toggleConsole():void + { + if (_showing) + hideConsole(); + else + showConsole(); + } + + public function showConsole():void + { + if (!_showing) + { + _showing = true; + visible = true; + stage.focus = _inputField; + stage.addEventListener(KeyboardEvent.KEY_UP, onKeyPressInConsole); + _currHistoryIndex = _numCommandsInHistory; + _onShowConsole.dispatch(); + } + } + + public function hideConsole():void + { + if (_showing) + { + _showing = false; + visible = false; + stage.removeEventListener(KeyboardEvent.KEY_UP, onKeyPressInConsole); + _onHideConsole.dispatch(); + } + } + + public function clearConsole():void + { + _inputField.text = ""; + } + + private function onAddedToStage(event:Event):void + { + graphics.beginFill(0x000000, .8); + graphics.drawRect(0, 0, stage.stageWidth, 30); + graphics.endFill(); + + _inputField.width = stage.stageWidth; + _inputField.y = 4; + _inputField.x = 4; + + if (_enabled) + stage.addEventListener(KeyboardEvent.KEY_UP, onToggleKeyPress); + } + + private function onConsoleFocusOut(event:FocusEvent):void + { + hideConsole(); + } + + private function onToggleKeyPress(event:KeyboardEvent):void + { + if (event.keyCode == openKey) + { + toggleConsole(); + } + } + + private function onKeyPressInConsole(event:KeyboardEvent):void + { + if (event.keyCode == _executeKey) + { + if (_inputField.text == "" || _inputField.text == " ") + return; + + addCommandToHistory(_inputField.text); + + var args:Array = _inputField.text.split(" "); + var command:String = args.shift(); + clearConsole(); + hideConsole(); + + var func:Function = _commandDelegates[command]; + if (func != null) + { + try + { + func.apply(this, args); + } + catch(e:ArgumentError) + { + if (e.errorID == 1063) //Argument count mismatch on [some function]. Expected [x], got [y] + { + trace(e.message); + var expected:Number = Number(e.message.slice(e.message.indexOf("Expected ") + 9, e.message.lastIndexOf(","))); + var lessArgs:Array = args.slice(0, expected); + func.apply(this, lessArgs); + } + } + } + } + else if (event.keyCode == _prevHistoryKey) + { + _inputField.text = getPreviousHistoryCommand(); + event.preventDefault(); + _inputField.setSelection(_inputField.text.length, _inputField.text.length); + } + else if (event.keyCode == _nextHistoryKey) + { + _inputField.text = getNextHistoryCommand(); + event.preventDefault(); + _inputField.setSelection(_inputField.text.length, _inputField.text.length); + } + } + } +} \ No newline at end of file diff --git a/src/citrus/core/IState.as b/src/citrus/core/IState.as new file mode 100644 index 00000000..abe07eea --- /dev/null +++ b/src/citrus/core/IState.as @@ -0,0 +1,33 @@ +package citrus.core { + + import citrus.system.Entity; + import citrus.view.ACitrusView; + + /** + * Take a look on the 2 respective states to have some information on the functions. + */ + public interface IState { + + function destroy():void; + + function get view():ACitrusView; + + function initialize():void; + + function update(timeDelta:Number):void; + + function add(object:CitrusObject):CitrusObject; + + function addEntity(entity:Entity):Entity; + + function remove(object:CitrusObject):void; + + function removeImmediately(object:CitrusObject):void; + + function getObjectByName(name:String):CitrusObject; + + function getFirstObjectByType(type:Class):CitrusObject; + + function getObjectsByType(type:Class):Vector.; + } +} diff --git a/src/citrus/core/MediatorState.as b/src/citrus/core/MediatorState.as new file mode 100644 index 00000000..85b1ccaf --- /dev/null +++ b/src/citrus/core/MediatorState.as @@ -0,0 +1,380 @@ +package citrus.core { + + import citrus.physics.APhysicsEngine; + import citrus.datastructures.PoolObject; + import citrus.objects.APhysicsObject; + import citrus.system.Component; + import citrus.system.Entity; + import citrus.system.components.ViewComponent; + import citrus.view.ACitrusView; + + /** + * The MediatorState class is very important. It usually contains the logic for a particular state the game is in. + * You should never instanciate/extend this class by your own. It's used via a wrapper: State or StarlingState or Away3DState. + * There can only ever be one state running at a time. You should extend the State class + * to create logic and scripts for your levels. You can build one state for each level, or + * create a state that represents all your levels. You can get and set the reference to your active + * state via the CitrusEngine class. + */ + final public class MediatorState { + + private var _objects:Vector. = new Vector.(); + private var _poolObjects:Vector. = new Vector.(); + private var _view:ACitrusView; + private var _istate:IState; + + private var _garbage:Array = []; + private var _numObjects:uint = 0; + + public function MediatorState(istate:IState) { + _istate = istate; + } + + /** + * Called by the Citrus Engine. + */ + public function destroy():void { + + for each (var poolObject:PoolObject in _poolObjects) + poolObject.destroy(); + + _poolObjects.length = 0; + + _numObjects = _objects.length; + var co:CitrusObject; + while((co = _objects.pop()) != null) + removeImmediately(co); + _numObjects = _objects.length = 0; + + _view.destroy(); + + _objects = null; + _poolObjects = null; + _view = null; + } + + /** + * Gets a reference to this state's view manager. Take a look at the class definition for more information about this. + */ + public function get view():ACitrusView { + return _view; + } + + public function set view(value:ACitrusView):void { + + _view = value; + } + + /** + * This method calls update on all the CitrusObjects that are attached to this state. + * The update method also checks for CitrusObjects that are ready to be destroyed and kills them. + * Finally, this method updates the View manager. + */ + public function update(timeDelta:Number):void { + + _numObjects = _objects.length; + + var object:CitrusObject; + + for (var i:uint = 0; i < _numObjects; ++i) { //run through objects from 'left' to 'right' + + object = _objects.shift(); // get first object in list + + if (object.kill) + _garbage.push(object); // push object to garbage + + else { + _objects.push(object); // re-insert object at the end of _objects + + if (object.updateCallEnabled) + object.update(timeDelta); + } + } + + // Destroy all objects marked for destroy + // TODO There might be a limit on the number of Box2D bodies that you can destroy in one tick? + var garbageObject:CitrusObject; + while((garbageObject = _garbage.shift()) != null) + removeImmediately(garbageObject); + + for each (var poolObject:PoolObject in _poolObjects) + poolObject.updatePhysics(timeDelta); + + // Update the state's view + _view.update(timeDelta); + } + + /** + * Call this method to add a CitrusObject to this state. All visible game objects and physics objects + * will need to be created and added via this method so that they can be properly created, managed, updated, and destroyed. + * @return The CitrusObject that you passed in. Useful for linking commands together. + */ + public function add(object:CitrusObject):CitrusObject { + + if (object is Entity) + throw new Error("Object named: " + object.name + " is an entity and should be added to the state via addEntity method."); + + for each (var objectAdded:CitrusObject in objects) + if (object == objectAdded) + throw new Error(object.name + " is already added to the state."); + + if (object is APhysicsObject) + (object as APhysicsObject).addPhysics(); + + if(object is APhysicsEngine) + _objects.unshift(object); + else + _objects.push(object); + + _view.addArt(object); + + return object; + } + + /** + * Call this method to add an Entity to this state. All entities will need to be created + * and added via this method so that they can be properly created, managed, updated, and destroyed. + * @return The Entity that you passed in. Useful for linking commands together. + */ + public function addEntity(entity:Entity):Entity { + + for each (var objectAdded:CitrusObject in objects) + if (entity == objectAdded) + throw new Error(entity.name + " is already added to the state."); + + _objects.push(entity); + + var views:Vector. = entity.lookupComponentsByType(ViewComponent); + if (views.length > 0) + for each(var view:ViewComponent in views) + { + _view.addArt(view); + } + + return entity; + } + + /** + * Call this method to add a PoolObject to this state. All pool objects and will need to be created + * and added via this method so that they can be properly created, managed, updated, and destroyed. + * @param poolObject The PoolObject isCitrusObjectPool's value must be true to be render through the State. + * @return The PoolObject that you passed in. Useful for linking commands together. + */ + public function addPoolObject(poolObject:PoolObject):PoolObject { + + if (poolObject.isCitrusObjectPool) { + poolObject.citrus_internal::state = _istate; + _poolObjects.push(poolObject); + + return poolObject; + + } else return null; + } + + /** + * removeImmediately instaneously destroys and remove the object from the state. + * + * While using remove() is recommended, there are specific case where this is needed. + * please use with care. + */ + public function remove(object:CitrusObject):void { + object.kill = true; + } + + public function removeImmediately(object:CitrusObject):void { + if(object == null) + return; + + var i:uint = _objects.indexOf(object); + + if(i < 0) + return; + + object.kill = true; + _objects.splice(i, 1); + + if (object is Entity) { + var views:Vector. = (object as Entity).lookupComponentsByType(ViewComponent); + + if (views.length > 0) + for each(var view:ViewComponent in views) + _view.removeArt(view); + + } else + _view.removeArt(object); + + object.destroy(); + + --_numObjects; + } + + /** + * Gets a reference to a CitrusObject by passing that object's name in. + * Often the name property will be set via a level editor such as the Flash IDE. + * @param name The name property of the object you want to get a reference to. + */ + public function getObjectByName(name:String):CitrusObject { + + for each (var object:CitrusObject in _objects) { + if (object.name == name) + return object; + } + + if (_poolObjects.length > 0) + { + var poolObject:PoolObject; + var found:Boolean = false; + for each(poolObject in _poolObjects) + { + poolObject.foreachRecycled(function(pobject:*):Boolean + { + if (pobject is CitrusObject && pobject["name"] == name) + { + object = pobject; + return found = true; + } + return false; + }); + + if (found) + return object; + } + } + + return null; + } + + /** + * This returns a vector of all objects of a particular name. This is useful for adding an event handler + * to objects that aren't similar but have the same name. For instance, you can track the collection of + * coins plus enemies that you've named exactly the same. Then you'd loop through the returned vector to change properties or whatever you want. + * @param name The name property of the object you want to get a reference to. + */ + public function getObjectsByName(name:String):Vector. { + + var objects:Vector. = new Vector.(); + var object:CitrusObject; + + for each (object in _objects) { + if (object.name == name) + objects.push(object); + } + + if (_poolObjects.length > 0) + { + var poolObject:PoolObject; + for each(poolObject in _poolObjects) + { + poolObject.foreachRecycled(function(pobject:*):Boolean + { + if (pobject is CitrusObject && pobject["name"] == name) + objects.push(pobject as CitrusObject); + return false; + }); + } + } + + return objects; + } + + /** + * Returns the first instance of a CitrusObject that is of the class that you pass in. + * This is useful if you know that there is only one object of a certain time in your state (such as a "Hero"). + * @param type The class of the object you want to get a reference to. + */ + public function getFirstObjectByType(type:Class):CitrusObject { + var object:CitrusObject; + + for each (object in _objects) { + if (object is type) + return object; + } + + if (_poolObjects.length > 0) + { + var poolObject:PoolObject; + var found:Boolean = false; + for each(poolObject in _poolObjects) + { + poolObject.foreachRecycled(function(pobject:*):Boolean + { + if (pobject is type) + { + object = pobject; + return found = true; + } + return false; + }); + + if (found) + return object; + } + } + + return null; + } + + /** + * This returns a vector of all objects of a particular type. This is useful for adding an event handler + * to all similar objects. For instance, if you want to track the collection of coins, you can get all objects + * of type "Coin" via this method. Then you'd loop through the returned array to add your listener to the coins' event. + * @param type The class of the object you want to get a reference to. + */ + public function getObjectsByType(type:Class):Vector. { + + var objects:Vector. = new Vector.(); + var object:CitrusObject; + + for each (object in _objects) { + if (object is type) { + objects.push(object); + } + } + + if (_poolObjects.length > 0) + { + var poolObject:PoolObject; + for each(poolObject in _poolObjects) + { + poolObject.foreachRecycled(function(pobject:*):Boolean + { + if (pobject is type) + objects.push(pobject as CitrusObject); + return false; + }); + } + } + + return objects; + } + + /** + * Destroy all the objects added to the State and not already killed. + * @param except CitrusObjects you want to save. + */ + public function killAllObjects(except:Array):void { + + for each (var objectToKill:CitrusObject in _objects) { + + objectToKill.kill = true; + + for each (var objectToPreserve:CitrusObject in except) { + + if (objectToKill == objectToPreserve) { + + objectToPreserve.kill = false; + except.splice(except.indexOf(objectToPreserve), 1); + break; + } + } + } + } + + /** + * Contains all the objects added to the State and not killed. + */ + public function get objects():Vector. { + return _objects; + } + } +} \ No newline at end of file diff --git a/src/citrus/core/State.as b/src/citrus/core/State.as new file mode 100644 index 00000000..6c0c6e0a --- /dev/null +++ b/src/citrus/core/State.as @@ -0,0 +1,185 @@ +package citrus.core { + + import citrus.datastructures.PoolObject; + import citrus.input.Input; + import citrus.system.Entity; + import citrus.system.components.ViewComponent; + import citrus.view.ACitrusView; + import citrus.view.spriteview.SpriteView; + + import flash.display.Sprite; + + /** + * State class is just a wrapper for the AState class. It's important to notice it extends Sprite. + */ + public class State extends Sprite implements IState { + + /** + * Get a direct references to the Citrus Engine in your State. + */ + protected var _ce:CitrusEngine; + + protected var _realState:MediatorState; + + protected var _input:Input; + + public function State() { + + _ce = CitrusEngine.getInstance(); + + _realState = new MediatorState(this); + } + + /** + * Called by the Citrus Engine. + */ + public function destroy():void { + _realState.destroy(); + _realState = null; + } + + /** + * Gets a reference to this state's view manager. Take a look at the class definition for more information about this. + */ + public function get view():ACitrusView { + return _realState.view; + } + + /** + * You'll most definitely want to override this method when you create your own State class. This is where you should + * add all your CitrusObjects and pretty much make everything. Please note that you can't successfully call add() on a + * state in the constructur. You should call it in this initialize() method. + */ + public function initialize():void { + _realState.view = createView(); + _input = _ce.input; + } + + /** + * This method calls update on all the CitrusObjects that are attached to this state. + * The update method also checks for CitrusObjects that are ready to be destroyed and kills them. + * Finally, this method updates the View manager. + */ + public function update(timeDelta:Number):void { + + _realState.update(timeDelta); + } + + /** + * Call this method to add a CitrusObject to this state. All visible game objects and physics objects + * will need to be created and added via this method so that they can be properly created, managed, updated, and destroyed. + * @return The CitrusObject that you passed in. Useful for linking commands together. + */ + public function add(object:CitrusObject):CitrusObject { + return _realState.add(object); + } + + /** + * Call this method to add an Entity to this state. All entities will need to be created + * and added via this method so that they can be properly created, managed, updated, and destroyed. + * @return The Entity that you passed in. Useful for linking commands together. + */ + public function addEntity(entity:Entity):Entity { + + return _realState.addEntity(entity); + } + + /** + * Call this method to add a PoolObject to this state. All pool objects and will need to be created + * and added via this method so that they can be properly created, managed, updated, and destroyed. + * @param poolObject The PoolObject isCitrusObjectPool's value must be true to be render through the State. + * @return The PoolObject that you passed in. Useful for linking commands together. + */ + public function addPoolObject(poolObject:PoolObject):PoolObject { + + return _realState.addPoolObject(poolObject); + } + + /** + * When you are ready to remove an object from getting updated, viewed, and generally being existent, call this method. + * Alternatively, you can just set the object's kill property to true. That's all this method does at the moment. + */ + public function remove(object:CitrusObject):void { + _realState.remove(object); + } + + /** + * removeImmediately instaneously destroys and remove the object from the state. + * + * While using remove() is recommended, there are specific case where this is needed. + * please use with care. + * + * Warning: + * - can break box2D if called directly or indirectly in a collision listener. + * - effects unknown with nape. + */ + public function removeImmediately(object:CitrusObject):void { + _realState.removeImmediately(object); + } + + /** + * Gets a reference to a CitrusObject by passing that object's name in. + * Often the name property will be set via a level editor such as the Flash IDE. + * @param name The name property of the object you want to get a reference to. + */ + public function getObjectByName(name:String):CitrusObject { + + return _realState.getObjectByName(name); + } + + /** + * This returns a vector of all objects of a particular name. This is useful for adding an event handler + * to objects that aren't similar but have the same name. For instance, you can track the collection of + * coins plus enemies that you've named exactly the same. Then you'd loop through the returned vector to change properties or whatever you want. + * @param name The name property of the object you want to get a reference to. + */ + public function getObjectsByName(name:String):Vector. { + + return _realState.getObjectsByName(name); + } + + /** + * Returns the first instance of a CitrusObject that is of the class that you pass in. + * This is useful if you know that there is only one object of a certain time in your state (such as a "Hero"). + * @param type The class of the object you want to get a reference to. + */ + public function getFirstObjectByType(type:Class):CitrusObject { + + return _realState.getFirstObjectByType(type); + } + + /** + * This returns a vector of all objects of a particular type. This is useful for adding an event handler + * to all similar objects. For instance, if you want to track the collection of coins, you can get all objects + * of type "Coin" via this method. Then you'd loop through the returned array to add your listener to the coins' event. + * @param type The class of the object you want to get a reference to. + */ + public function getObjectsByType(type:Class):Vector. { + + return _realState.getObjectsByType(type); + } + + /** + * Destroy all the objects added to the State and not already killed. + * @param except CitrusObjects you want to save. + */ + public function killAllObjects(...except):void { + + _realState.killAllObjects(except); + } + + /** + * Contains all the objects added to the State and not killed. + */ + public function get objects():Vector. { + return _realState.objects; + } + + /** + * Override this method if you want a state to create an instance of a custom view. + */ + protected function createView():ACitrusView { + return new SpriteView(this); + } + } +} \ No newline at end of file diff --git a/src/citrus/core/citrus_internal.as b/src/citrus/core/citrus_internal.as new file mode 100644 index 00000000..bc39676d --- /dev/null +++ b/src/citrus/core/citrus_internal.as @@ -0,0 +1,7 @@ +package citrus.core +{ + /** + * namespace definition + */ + public namespace citrus_internal = "http://www.citrusengine.com/"; +} \ No newline at end of file diff --git a/src/citrus/core/starling/CitrusStarlingJuggler.as b/src/citrus/core/starling/CitrusStarlingJuggler.as new file mode 100644 index 00000000..4e508f0c --- /dev/null +++ b/src/citrus/core/starling/CitrusStarlingJuggler.as @@ -0,0 +1,25 @@ +package citrus.core.starling +{ + import starling.animation.Juggler; + + /** + * A Custom Starling Juggler used by CitrusEngine for pausing. + */ + public class CitrusStarlingJuggler extends Juggler + { + public var paused:Boolean = false; + + public function CitrusStarlingJuggler() + { + super(); + } + + override public function advanceTime(timeDelta:Number):void + { + if (!paused) + super.advanceTime(timeDelta); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/core/starling/StarlingCitrusEngine.as b/src/citrus/core/starling/StarlingCitrusEngine.as new file mode 100644 index 00000000..6ffa4b2e --- /dev/null +++ b/src/citrus/core/starling/StarlingCitrusEngine.as @@ -0,0 +1,387 @@ +package citrus.core.starling { + + import citrus.core.CitrusEngine; + import citrus.core.State; + + import starling.core.Starling; + import starling.events.Event; + import starling.utils.RectangleUtil; + import starling.utils.ScaleMode; + + import flash.display.Stage3D; + import flash.events.Event; + import flash.geom.Rectangle; + + /** + * Extends this class if you create a Starling based game. Don't forget to call setUpStarling function. + * + *
    + */ + public class StarlingCitrusEngine extends CitrusEngine { + + public var scaleFactor:Number = 1; + + protected var _starling:Starling; + protected var _juggler:CitrusStarlingJuggler; + + protected var _assetSizes:Array = [1]; + protected var _baseWidth:int = -1; + protected var _baseHeight:int = -1; + protected var _viewportMode:String = ViewportMode.LEGACY; + protected var _viewport:Rectangle; + protected var _suspendRenderingOnDeactivate:Boolean = false; + + private var _viewportBaseRatioWidth:Number = 1; + private var _viewportBaseRatioHeight:Number = 1; + + /** + * context3D profiles to test for in Ascending order (the more important first). + * reset this array to a single entry to force one specific profile. More informations. + */ + protected var _context3DProfiles:Array = ["standardExtended", "standard", "standardConstrained", "baselineExtended", "baseline", "baselineConstrained"]; + + public function StarlingCitrusEngine() { + super(); + + _juggler = new CitrusStarlingJuggler(); + } + + /** + * @inheritDoc + */ + override public function destroy():void { + + super.destroy(); + + _juggler.purge(); + + if (_state) { + + if (_starling) { + _starling.stage.removeEventListener(starling.events.Event.RESIZE, handleStarlingStageResize); + _starling.stage.removeChild(_state as StarlingState); + _starling.root.dispose(); + _starling.dispose(); + } + } + } + + /** + * @inheritDoc + */ + override protected function handlePlayingChange(value:Boolean):void + { + super.handlePlayingChange(value); + + _juggler.paused = !value; + } + + /** + * You should call this function to create your Starling view. The RootClass is internal, it is never used elsewhere. + * StarlingState is added on the starling stage : _starling.stage.addChildAt(_state as StarlingState, _stateDisplayIndex); + * @param debugMode If true, display a Stats class instance. + * @param antiAliasing The antialiasing value allows you to set the anti-aliasing (0 - 16), generally a value of 1 is totally acceptable. + * @param viewPort Starling's viewport, default is (0, 0, stage.stageWidth, stage.stageHeight, change to (0, 0, stage.fullScreenWidth, stage.fullScreenHeight) for mobile. + * @param stage3D The reference to the Stage3D, useful for sharing a 3D context. More informations. + */ + public function setUpStarling(debugMode:Boolean = false, antiAliasing:uint = 1, viewPort:Rectangle = null, stage3D:Stage3D = null):void { + + Starling.handleLostContext = true; + + if (viewPort) + _viewport = viewPort; + + _starling = new Starling(RootClass, stage, null, stage3D, "auto", _context3DProfiles); + _starling.antiAliasing = antiAliasing; + _starling.showStats = debugMode; + _starling.addEventListener(starling.events.Event.CONTEXT3D_CREATE, _context3DCreated); + _starling.stage.addEventListener(starling.events.Event.RESIZE, handleStarlingStageResize); + } + + protected function handleStarlingStageResize(evt:starling.events.Event):void { + + resetScreenSize(); + onStageResize.dispatch(_screenWidth, _screenHeight); + } + + /** + * returns the asset size closest to one of the available asset sizes you have (based on Starling.contentScaleFactor). + * If you design your app with a Starling's stage dimension equals to the Flash's stage dimension, you will have to overwrite + * this function since the Starling.contentScaleFactor will be always equal to 1. + * @param assetSizes Array of numbers listing all asset sizes you use + * @return + */ + protected function findScaleFactor(assetSizes:Array):Number + { + var arr:Array = assetSizes; + arr.sort(Array.NUMERIC); + var scaleF:Number = Math.floor(starling.contentScaleFactor * 1000) / 1000; + var closest:Number; + var f:Number; + for each (f in arr) + if (!closest || Math.abs(f - scaleF) < Math.abs(closest - scaleF)) + closest = f; + + return closest; + } + + protected function resetViewport():Rectangle + { + if (_baseHeight < 0) + _baseHeight = _screenHeight; + if (_baseWidth < 0) + _baseWidth = _screenWidth; + + var baseRect:Rectangle = new Rectangle(0, 0, _baseWidth, _baseHeight); + var screenRect:Rectangle = new Rectangle(0, 0, _screenWidth, _screenHeight); + + switch(_viewportMode) + { + case ViewportMode.LETTERBOX: + _viewport = RectangleUtil.fit(baseRect, screenRect, ScaleMode.SHOW_ALL); + _viewport.x = _screenWidth * .5 - _viewport.width * .5; + _viewport.y = _screenHeight * .5 - _viewport.height * .5; + if (_starling) + { + _starling.stage.stageWidth = _baseWidth; + _starling.stage.stageHeight = _baseHeight; + } + + break; + case ViewportMode.FULLSCREEN: + _viewport = RectangleUtil.fit(baseRect, screenRect, ScaleMode.SHOW_ALL); + _viewportBaseRatioWidth = _viewport.width / baseRect.width; + _viewportBaseRatioHeight = _viewport.height / baseRect.height; + _viewport.copyFrom(screenRect); + + _viewport.x = 0; + _viewport.y = 0; + + if (_starling) + { + _starling.stage.stageWidth = screenRect.width / _viewportBaseRatioWidth; + _starling.stage.stageHeight = screenRect.height / _viewportBaseRatioHeight; + } + + break; + case ViewportMode.NO_SCALE: + _viewport = baseRect; + _viewport.x = _screenWidth * .5 - _viewport.width * .5; + _viewport.y = _screenHeight * .5 - _viewport.height * .5; + + if (_starling) + { + _starling.stage.stageWidth = _baseWidth; + _starling.stage.stageHeight = _baseHeight; + } + + break; + case ViewportMode.LEGACY: + _viewport = screenRect; + if (_starling) + { + _starling.stage.stageWidth = screenRect.width; + _starling.stage.stageHeight = screenRect.height; + } + case ViewportMode.MANUAL: + if(!_viewport) + _viewport = _starling.viewPort.clone(); + break; + } + + scaleFactor = findScaleFactor(_assetSizes); + + if (_starling) + { + transformMatrix.identity(); + transformMatrix.scale(_starling.contentScaleFactor,_starling.contentScaleFactor); + transformMatrix.translate(_viewport.x,_viewport.y); + } + + return _viewport; + } + + /** + * @inheritDoc + */ + override protected function resetScreenSize():void + { + super.resetScreenSize(); + + if (!_starling) + return; + + resetViewport(); + _starling.viewPort.copyFrom(_viewport); + + setupStats(); + } + + public function setupStats(hAlign:String = "left",vAlign:String = "top",scale:Number = 1):void + { + if(_starling && _starling.showStats) + _starling.showStatsAt(hAlign, vAlign,scale/_starling.contentScaleFactor); + } + + /** + * Be sure that starling is initialized (especially on mobile). + */ + protected function _context3DCreated(evt:starling.events.Event):void { + + _starling.removeEventListener(starling.events.Event.CONTEXT3D_CREATE, _context3DCreated); + + resetScreenSize(); + + if (!_starling.isStarted) + _starling.start(); + + _starling.addEventListener(starling.events.Event.ROOT_CREATED, _starlingRootCreated); + } + + protected function _starlingRootCreated(evt:starling.events.Event):void { + + _starling.removeEventListener(starling.events.Event.ROOT_CREATED, _starlingRootCreated); + + stage.removeEventListener(flash.events.Event.RESIZE, handleStageResize); + + handleStarlingReady(); + setupStats(); + } + + /** + * This function is called when context3D is ready and the starling root is created. + * the idea is to use this function for asset loading through the starling AssetManager and create the first state. + */ + public function handleStarlingReady():void { + } + + public function get starling():Starling { + return _starling; + } + + /** + * @inheritDoc + */ + override protected function handleEnterFrame(e:flash.events.Event):void { + + if (_starling && _starling.isStarted && _starling.context) { + + if (_newState) { + + if (_state) { + + if (_state is StarlingState) { + + _state.destroy(); + _starling.stage.removeChild(_state as StarlingState, true); + + } else if(_newState is StarlingState) { + + _state.destroy(); + removeChild(_state as State); + } + + } + + if (_newState is StarlingState) { + + _state = _newState; + _newState = null; + + if (_futureState) + _futureState = null; + else { + _starling.stage.addChildAt(_state as StarlingState, _stateDisplayIndex); + _state.initialize(); + } + } + } + + if (_stateTransitionning && _stateTransitionning is StarlingState) { + + _futureState = _stateTransitionning; + _stateTransitionning = null; + + starling.stage.addChildAt(_futureState as StarlingState, _stateDisplayIndex); + _futureState.initialize(); + } + + } + + super.handleEnterFrame(e); + + if(_juggler) + _juggler.advanceTime(_timeDelta); + + } + + /** + * @inheritDoc + * We stop Starling. Be careful, if you use AdMob you will need to override this function and set Starling stop to true! + * If you encounter issues with AdMob, you may override handleStageDeactivated and handleStageActivated and use NativeApplication.nativeApplication instead. + */ + override protected function handleStageDeactivated(e:flash.events.Event):void { + + if (_playing && _starling) + _starling.stop(_suspendRenderingOnDeactivate); + + super.handleStageDeactivated(e); + } + + /** + * @inheritDoc + * We start Starling. + */ + override protected function handleStageActivated(e:flash.events.Event):void { + + if (_starling && !_starling.isStarted) + _starling.start(); + + super.handleStageActivated(e); + } + + public function get baseWidth():int + { + return _baseWidth; + } + + public function set baseWidth(value:int):void { + + _baseWidth = value; + + resetViewport(); + } + + public function get baseHeight():int + { + return _baseHeight; + } + + public function set baseHeight(value:int):void { + + _baseHeight = value; + + resetViewport(); + } + + public function get juggler():CitrusStarlingJuggler + { + return _juggler; + } + + } +} + + + +import starling.display.Sprite; + + +/** + * RootClass is the root of Starling, it is never destroyed and only accessed through _starling.stage. + */ +internal class RootClass extends Sprite { + + public function RootClass() { + } +} diff --git a/src/citrus/core/starling/StarlingState.as b/src/citrus/core/starling/StarlingState.as new file mode 100644 index 00000000..06e53bca --- /dev/null +++ b/src/citrus/core/starling/StarlingState.as @@ -0,0 +1,197 @@ +package citrus.core.starling { + + import citrus.core.CitrusEngine; + import citrus.core.CitrusObject; + import citrus.core.IState; + import citrus.core.MediatorState; + import citrus.datastructures.PoolObject; + import citrus.input.Input; + import citrus.system.Entity; + import citrus.view.ACitrusView; + import citrus.view.starlingview.StarlingCamera; + import citrus.view.starlingview.StarlingView; + + import starling.display.Sprite; + + /** + * StarlingState class is just a wrapper for the AState class. It's important to notice it extends Starling Sprite. + */ + public class StarlingState extends Sprite implements IState { + + /** + * Get a direct references to the Citrus Engine in your State. + */ + protected var _ce:StarlingCitrusEngine; + + protected var _realState:MediatorState; + + protected var _input:Input; + + public function StarlingState() { + + _ce = CitrusEngine.getInstance() as StarlingCitrusEngine; + + if (!(_ce as StarlingCitrusEngine) || !(_ce as StarlingCitrusEngine).starling) + throw new Error("Your Main " + _ce + " class doesn't extend StarlingCitrusEngine, or you didn't call its setUpStarling function"); + + _realState = new MediatorState(this); + } + + /** + * Called by the Citrus Engine. + */ + public function destroy():void { + _realState.destroy(); + } + + /** + * Gets a reference to this state's view manager. Take a look at the class definition for more information about this. + */ + public function get view():ACitrusView { + return _realState.view; + } + + /** + * You'll most definitely want to override this method when you create your own State class. This is where you should + * add all your CitrusObjects and pretty much make everything. Please note that you can't successfully call add() on a + * state in the constructur. You should call it in this initialize() method. + */ + public function initialize():void { + _realState.view = createView(); + _input = _ce.input; + } + + /** + * This method calls update on all the CitrusObjects that are attached to this state. + * The update method also checks for CitrusObjects that are ready to be destroyed and kills them. + * Finally, this method updates the View manager. + */ + public function update(timeDelta:Number):void { + + _realState.update(timeDelta); + } + + /** + * Call this method to add a CitrusObject to this state. All visible game objects and physics objects + * will need to be created and added via this method so that they can be properly created, managed, updated, and destroyed. + * @return The CitrusObject that you passed in. Useful for linking commands together. + */ + public function add(object:CitrusObject):CitrusObject { + return _realState.add(object); + } + + /** + * Call this method to add an Entity to this state. All entities will need to be created + * and added via this method so that they can be properly created, managed, updated, and destroyed. + * @return The Entity that you passed in. Useful for linking commands together. + */ + public function addEntity(entity:Entity):Entity { + + return _realState.addEntity(entity); + } + + /** + * Call this method to add a PoolObject to this state. All pool objects and will need to be created + * and added via this method so that they can be properly created, managed, updated, and destroyed. + * @param poolObject The PoolObject isCitrusObjectPool's value must be true to be render through the State. + * @return The PoolObject that you passed in. Useful for linking commands together. + */ + public function addPoolObject(poolObject:PoolObject):PoolObject { + + return _realState.addPoolObject(poolObject); + } + + /** + * When you are ready to remove an object from getting updated, viewed, and generally being existent, call this method. + * Alternatively, you can just set the object's kill property to true. That's all this method does at the moment. + */ + public function remove(object:CitrusObject):void { + _realState.remove(object); + } + + /** + * removeImmediately instaneously destroys and remove the object from the state. + * + * While using remove() is recommended, there are specific case where this is needed. + * please use with care. + * + * Warning: + * - can break box2D if called directly or indirectly in a collision listener. + * - effects unknown with nape. + */ + public function removeImmediately(object:CitrusObject):void { + _realState.removeImmediately(object); + } + + + /** + * Gets a reference to a CitrusObject by passing that object's name in. + * Often the name property will be set via a level editor such as the Flash IDE. + * @param name The name property of the object you want to get a reference to. + */ + public function getObjectByName(name:String):CitrusObject { + + return _realState.getObjectByName(name); + } + + /** + * This returns a vector of all objects of a particular name. This is useful for adding an event handler + * to objects that aren't similar but have the same name. For instance, you can track the collection of + * coins plus enemies that you've named exactly the same. Then you'd loop through the returned vector to change properties or whatever you want. + * @param name The name property of the object you want to get a reference to. + */ + public function getObjectsByName(name:String):Vector. { + + return _realState.getObjectsByName(name); + } + + /** + * Returns the first instance of a CitrusObject that is of the class that you pass in. + * This is useful if you know that there is only one object of a certain time in your state (such as a "Hero"). + * @param type The class of the object you want to get a reference to. + */ + public function getFirstObjectByType(type:Class):CitrusObject { + + return _realState.getFirstObjectByType(type); + } + + /** + * This returns a vector of all objects of a particular type. This is useful for adding an event handler + * to all similar objects. For instance, if you want to track the collection of coins, you can get all objects + * of type "Coin" via this method. Then you'd loop through the returned array to add your listener to the coins' event. + * @param type The class of the object you want to get a reference to. + */ + public function getObjectsByType(type:Class):Vector. { + + return _realState.getObjectsByType(type); + } + + /** + * Destroy all the objects added to the State and not already killed. + * @param except CitrusObjects you want to save. + */ + public function killAllObjects(...except):void { + + _realState.killAllObjects(except); + } + + /** + * Contains all the objects added to the State and not killed. + */ + public function get objects():Vector. { + return _realState.objects; + } + + /** + * Override this method if you want a state to create an instance of a custom view. + */ + protected function createView():ACitrusView { + return new StarlingView(this); + } + + public function get camera():StarlingCamera + { + return view.camera as StarlingCamera; + } + } +} diff --git a/src/citrus/core/starling/ViewportMode.as b/src/citrus/core/starling/ViewportMode.as new file mode 100644 index 00000000..6655718a --- /dev/null +++ b/src/citrus/core/starling/ViewportMode.as @@ -0,0 +1,36 @@ +package citrus.core.starling +{ + public class ViewportMode + { + /** + * The viewport will fit the screen as best as it can, keeping the original aspect ratio, thus leaving horizontal or vertical borders + * where nothing will be rendered. + */ + public static const LETTERBOX:String = "LETTERBOX"; + + /** + * The viewport will be centered, with the game's base dimensions. + */ + public static const NO_SCALE:String = "NO_SCALE"; + + /** + * The viewport will be as wide and tall as the screen, but the stage will be the base width and height dimensions, extended + * horizontally or vertically to keep the aspect ratio. This mode corresponds to Strategy 3 on the multiresolution wiki article for starling. + */ + public static const FULLSCREEN:String = "FULLSCREEN"; + + /** + * Legacy mode will make the viewport fill the screen as well as set the starling stage dimensions to the flash stage dimensions + * as what used to happen by default in CE prior to 3.1.8. + */ + public static const LEGACY:String = "LEGACY"; + + /** + * Manual mode : + * if the StarlingCitrusEngine.viewport rectangle is not defined, it will be defined as the flash stageWidth/stageHeight. + * if you have defined it in your StarlingCitrusEngine, it will be used as the starling viewport and you are in charge of defining its position or the starling stage dimensions. + */ + public static const MANUAL:String = "MANUAL"; + } + +} \ No newline at end of file diff --git a/src/citrus/datastructures/BitFlag.as b/src/citrus/datastructures/BitFlag.as new file mode 100644 index 00000000..12c51fd0 --- /dev/null +++ b/src/citrus/datastructures/BitFlag.as @@ -0,0 +1,347 @@ +/** + * A class for using bit flags in an object. Explanations. + * @author Damian Connolly - http://divillysausages.com + */ +package citrus.datastructures { + + import flash.system.System; + import flash.utils.Dictionary; + import flash.utils.describeType; + + public class BitFlag { + /*************************************************************************************************************/ + + private static const MAX_INT:int = int.MAX_VALUE; // the max value we can have if using ints + private static const MAX_UINT:uint = uint.MAX_VALUE; // the max value we can have if using uints + + /*************************************************************************************************************/ + + private static var m_cache:Dictionary = null; // our cache for flag classes + + /*************************************************************************************************************/ + + /** + * Destroys the cache for the flag classes + */ + public static function destroyCache():void + { + // no cache, do nothing + if ( BitFlag.m_cache == null ) + return; + + // go through and clear all our objects + for ( var key:* in BitFlag.m_cache ) + { + BitFlag.m_cache[key] = null; + delete BitFlag.m_cache[key]; + } + + // kill our dictionary + BitFlag.m_cache = null; + } + + /*************************************************************************************************************/ + + private var m_flagClass:Class = null; // the class that we use to verify our flags + private var m_flags:uint = 0; // the flags that we have + + /*************************************************************************************************************/ + + /** + * The class that we use to verify our flags. Setting this will clear any flags that we have + */ + public function get flagClass():Class { return this.m_flagClass; } + public function set flagClass( c:Class ):void + { + // clear any flags that we have + this.m_flags = 0; + + // set our class and read the flags from it + this.m_flagClass = c; + if ( this.m_flagClass != null ) + this._setupFlagClass(); + } + + /*************************************************************************************************************/ + + /** + * Creates the bit flag object + * @param flagClass The class that we'll use for our constants if we want to check the flags passed + */ + public function BitFlag( flagClass:Class = null ) + { + this.flagClass = flagClass; + } + + /** + * Destroys the bit flag object and clears it for garbage collection + */ + public function destroy():void + { + this.flagClass = null; + } + + /** + * Adds a flag to our object + * @param flag The flag that we want to add + */ + public function addFlag( flag:uint ):void + { + // clean our flags if needed + this.m_flags |= ( this.m_flagClass != null ) ? this._cleanFlags( flag ) : flag; + } + + /** + * Adds a list of flags to our object + * @param flags The list of flags that we want to add + */ + public function addFlags( ... flags ):void + { + // Add all the flags (clean if needed) + var len:int = flags.length; + for ( var i:int = 0; i < len; i++ ) + this.m_flags |= ( this.m_flagClass != null ) ? this._cleanFlags( flags[i] ) : flags[i]; + } + + /** + * Removes a flag from our object + * @param flag The flag that we want to remove + */ + public function removeFlag( flag:uint ):void + { + // clean our flags if needed + this.m_flags &= ( this.m_flagClass != null ) ? ~this._cleanFlags( flag ) : ~flag; + } + + /** + * Removes a list of flags from our object + * @param The list of flags that we want to remove + */ + public function removeFlags( ... flags ):void + { + // remove all the flags (clean if needed) + var len:int = flags.length; + for ( var i:int = 0; i < len; i++ ) + this.m_flags &= ( this.m_flagClass != null ) ? ~this._cleanFlags( flags[i] ) : ~flags[i]; + } + + /** + * Simple utility to remove all flags at once. + */ + public function removeAllFlags():void + { + this.m_flags = 0; + } + + /** + * removes all previous flags and sets new flag/flags + * @param flag a flag or a list of flags (piped). + */ + public function setFlags( flag:uint ):void + { + // clean the flags if needed + this.m_flags = ( this.m_flagClass != null ) ? this._cleanFlags( flag ) : flag; + } + + /** + * Toggles a specific flag. If the current flag is false, this will set + * it to true and vice versa + * @param flag The flag that we want to toggle + */ + public function toggleFlag( flag:uint ):void + { + // clean the flags if needed + this.m_flags ^= ( this.m_flagClass != null ) ? this._cleanFlags( flag ) : flag; + } + + /** + * Toggles a list of flags on our object. If a flag is currently false, this + * will set it to true and vice versa + * @param flags The list of flags that we want to toggle + */ + public function toggleFlags( ... flags ):void + { + // toggle all the flags (clean if needed) + var len:int = flags.length; + for ( var i:int = 0; i < len; i++ ) + this.m_flags ^= ( this.m_flagClass != null ) ? this._cleanFlags( flags[i] ) : flags[i]; + } + + /** + * Checks if we have a specific flag set for this class. If the flag passed in is multiple + * flags (i.e. Flag1 | Flag2 | Flag3), then this will return true only if we have all the flags + * @param flag The flag that we want to check + * @return True if we have the flag, false otherwise + */ + public function hasFlag( flag:uint ):Boolean + { + // check if we have the flag (clean if needed) + flag = ( this.m_flagClass != null ) ? this._cleanFlags( flag ) : flag; + return ( this.m_flags & flag ) == flag; + } + + /** + * Checks if we have all the flags provided set + * @param flags The list of flags that we want to check + * @return True if all the flags are set, false otherwise + */ + public function hasFlags( ... flags ):Boolean + { + // concat up our flag to check + var allFlags:int = 0; + var len:int = flags.length; + for ( var i:int = 0; i < len; i++ ) + allFlags |= flags[i]; + + // clean the flags if needed + if ( this.m_flagClass != null ) + allFlags = this._cleanFlags( allFlags ); + + // now check if all of the flags are set + return ( this.m_flags & allFlags ) == allFlags; // match all + } + + /** + * Checks if we have a specific flag set for this class (or flags can be piped) + * @param flag The flag that we want to check + * @return True if we have any of the flag, false otherwise + */ + public function hasAnyFlag( flag:uint ):Boolean + { + // check if we have the flag (clean if needed) + flag = ( this.m_flagClass != null ) ? this._cleanFlags( flag ) : flag; + return ( this.m_flags & flag ) != 0; + } + + /** + * Checks if we have any of the flags provided set + * @param flags The list of flags that we want to check + * @return True if any the flags are set, false otherwise + */ + public function hasAnyFlags( ... flags ):Boolean + { + // concat up our flag to check + var allFlags:int = 0; + var len:int = flags.length; + for ( var i:int = 0; i < len; i++ ) + allFlags |= flags[i]; + + // clean the flags if needed + if ( this.m_flagClass != null ) + allFlags = this._cleanFlags( allFlags ); + + // check if we have any of the flags + return ( this.m_flags & allFlags ) != 0; + } + + /** + * Returns a String representation of the object + */ + public function toString():String + { + return "[BitFlag flags:" + this.m_flags.toString( 2 ) + "]"; + } + + /*************************************************************************************************************/ + + // cleans any flags passed in to make sure they come from our class + private function _cleanFlags( flags:uint ):uint + { + // if we don't have a class, we're not verifying, so ignore + if ( this.m_flagClass == null ) + return flags; + + // if we don't have our vector for some reason do nothing + if ( BitFlag.m_cache == null || !( this.m_flagClass in BitFlag.m_cache ) ) + return flags; + + // get our vector + var v:Vector. = BitFlag.m_cache[this.m_flagClass]; + + // clean the flags + var len:int = v.length; + var retFlags:uint = 0; + for ( var i:int = 0; i < len; i++ ) + { + // if a flag in our class exists in this flag, remove it + if ( ( flags & v[i] ) != 0 ) + { + retFlags |= v[i]; + flags &= ~v[i]; + } + } + + // if we have something left over, then there was a problem + if ( flags != 0 ) + trace( "3:[BitFlag] While cleaning the flags, we found an unknown flag (" + flags + ") that doesn't exist in our flag class (" + this.m_flagClass + ")" ); + + // return the cleaned flags + return retFlags; + } + + // takes a class and extracts all the flags from it so we can check any flags we get + private function _setupFlagClass():void + { + // make sure we have a class + if ( this.m_flagClass == null ) + return; + + // if we already have it in our cache, ignore + if ( BitFlag.m_cache != null && ( this.m_flagClass in BitFlag.m_cache ) ) + return; + + // it's not there, describe the class + var x:XML = describeType( this.m_flagClass ); + + // get all the constants and take out any int and uints + for each ( var cx:XML in x.constant ) + { + // only take ints and uints + var type:String = cx.@type; + if ( type != "uint" && type != "int" ) + { + trace( "0:[BitFlag] Ignoring '" + cx.@name + "' from class " + this.m_flagClass + " as it's not an int or an uint" ); + continue; + } + + // if it's an int, make sure it's good + if ( type == "int" ) + { + var intFlag:int = this.m_flagClass[cx.@name]; + if ( intFlag < 0 || intFlag > BitFlag.MAX_INT ) // less than 0, or we've done something like (1<<31) + { + trace( "0:[BitFlag] Ignoring const '" + cx.@name + "' from class " + this.m_flagClass + " as out of range. The max possible flag for an int is (1 << 30)" ); + continue; + } + } + + // get our uint (convert ints) + var flag:uint = this.m_flagClass[cx.@name]; + + // make sure only one bit is set (i.e. it's a flag and not a number) + // this check only works on numbers less than (1 << 30), so do a check for MAX_UINT + if ( flag != ( flag & -flag ) && flag != BitFlag.MAX_UINT ) + { + trace( "0:[BitFlag] Ignoring const '" + cx.@name + "' from class " + this.m_flagClass + " as it's not a flag" ); + continue; + } + + // create our dictionary if needed + if ( BitFlag.m_cache == null ) + BitFlag.m_cache = new Dictionary; + + // create our vector for this class if needed + if ( !( this.m_flagClass in BitFlag.m_cache ) ) + BitFlag.m_cache[this.m_flagClass] = new Vector.; + + // add our const + BitFlag.m_cache[this.m_flagClass].push( flag ); + } + + // dispose of the xml immediately + System.disposeXML( x ); + } + + + } +} diff --git a/src/citrus/datastructures/DoublyLinkedList.as b/src/citrus/datastructures/DoublyLinkedList.as new file mode 100644 index 00000000..d395125e --- /dev/null +++ b/src/citrus/datastructures/DoublyLinkedList.as @@ -0,0 +1,210 @@ +package citrus.datastructures { + + /** + * A doubly linked list is a linked data structure that consists of a set of sequentially linked records called nodes. + * Each node contains two fields, called links, that are references to the previous and to the next node in the sequence of nodes. + */ + public class DoublyLinkedList { + + public var head:DoublyLinkedListNode; + public var tail:DoublyLinkedListNode; + + protected var _count:uint; + + public function DoublyLinkedList() { + + head = tail = null; + _count = 0; + } + + /** + * Append an object to the list. + * @param data an object of any type added at the end of the list. + * @return returns the tail. + */ + public function append(data:*):DoublyLinkedListNode { + + var node:DoublyLinkedListNode = new DoublyLinkedListNode(data); + + if (tail != null) { + + tail.next = node; + node.prev = tail; + tail = node; + + } else { + head = tail = node; + } + + ++_count; + + return tail; + } + + /** + * Append a node to the list. + * @param node a DoublyLinkedListNode object of any type added at the end of the list. + * @return returns the doublyLinkedList. + */ + public function appendNode(node:DoublyLinkedListNode):DoublyLinkedList { + + if (head != null) { + + tail.next = node; + node.prev = tail; + tail = node; + + } else { + head = tail = node; + } + + + ++_count; + + return this; + } + + /** + * Prepend an object to the list. + * @param data an object of any type added at the beginning of the list. + * @return returns the head. + */ + public function prepend(data:*):DoublyLinkedListNode { + + var node:DoublyLinkedListNode = new DoublyLinkedListNode(data); + + if (head != null) { + + head.prev = node; + node.next = head; + head = node; + + } else { + head = tail = node; + } + + ++_count; + + return head; + } + + /** + * Prepend a node to the list. + * @param node a DoublyLinkedListNode object of any type added at the beginning of the list. + * @return returns the doublyLinkedList. + */ + public function prependNode(node:DoublyLinkedListNode):DoublyLinkedList { + + if (head != null) { + + head.prev = node; + node.next = head; + head = node; + + } else { + head = tail = node; + } + + ++_count; + + return this; + } + + /** + * Remove a node from the list and return its data. + * @param node the node to remove from the list. + * @return returns the removed node data. + */ + public function removeNode(node:DoublyLinkedListNode):* { + + var data:* = node.data; + + var countChanged:Boolean = false; + + if (node == head) { + + removeHead(); + countChanged= true; + + } else { + node.prev.next = node.next; + } + + if (node == tail) { + + removeTail(); + countChanged = true; + + } else { + node.next.prev = node.prev; + } + + if (!countChanged) + --_count; + + return data; + + } + + public function removeHead():* { + + var node:DoublyLinkedListNode = head; + + if (head != null) { + + var data:* = node.data; + + head = head.next; + + if (head != null) { + head.prev = null ; + } + + --_count; + + return data; + } + } + + public function removeTail():* { + + var node:DoublyLinkedListNode = tail; + + if (tail != null) { + + var data:* = node.data; + + tail = tail.prev; + + if (tail != null) { + tail.next = null; + } + + --_count; + + return data; + } + } + + /** + * Get the lengh of the list. + * @return the list length. + */ + public function get length():uint { + return _count; + } + + public function content():String { + + var tmpHead:DoublyLinkedListNode = head; + var text:String = ''; + + while (tmpHead != null) { + text += String(tmpHead.data) + " "; + tmpHead = tmpHead.next; + } + + return text; + } + } +} diff --git a/src/citrus/datastructures/DoublyLinkedListNode.as b/src/citrus/datastructures/DoublyLinkedListNode.as new file mode 100644 index 00000000..2e70b080 --- /dev/null +++ b/src/citrus/datastructures/DoublyLinkedListNode.as @@ -0,0 +1,23 @@ +package citrus.datastructures { + + /** + * Each node is composed of a data and references (in other words, links) to the next and previous node in the sequence. + * This structure allows for efficient insertion or removal of elements from any position in the sequence. + */ + public class DoublyLinkedListNode { + + public var data:*; + public var next:DoublyLinkedListNode; + public var prev:DoublyLinkedListNode; + + /** + * A simple data node used for DoubleLinkedList and Pool + * @param obj untyped data stored in the node + */ + public function DoublyLinkedListNode(obj:* = null) { + + next = prev = null; + data = obj; + } + } +} diff --git a/src/citrus/datastructures/PoolObject.as b/src/citrus/datastructures/PoolObject.as new file mode 100644 index 00000000..9f8347e5 --- /dev/null +++ b/src/citrus/datastructures/PoolObject.as @@ -0,0 +1,499 @@ +package citrus.datastructures { + + import citrus.core.citrus_internal; + import citrus.core.CitrusObject; + import citrus.core.IState; + import citrus.view.ACitrusView; + + import org.osflash.signals.Signal; + + /** + * Object pooling is a data structure based on a simple observation : the ‘new’ operator is costly, + * memory allocation necessary for the object creation is a slow process. And Garbage Collection too! + * So object pooling idea is really simple : + * - create lots of object at the beginning of your level, if there is FPS reduction it shouldn't be a big problem. + * - if you need more objects during the game create many of them that can be use later. + * - destroy your object if you don’t need it anymore, but keep a link to it! So it will be reassign! + * - destroy all your objects and set them to null at the end of your level (garbage collector will work). + */ + public class PoolObject extends DoublyLinkedList { + + protected var _poolType:Class; + protected var _defaultParams:Object; + protected var _poolSize:uint = 0; + protected var _poolGrowthRate:uint = 0; + protected var _isCitrusObjectPool:Boolean; + + citrus_internal var state:IState; + + /** + * dispatches a Signal with a newly created object of type _pooType. + */ + public var onCreate:Signal; + /** + * dispatches a Signal with the disposed object of type _pooType. + */ + public var onDispose:Signal; + /** + * dispatches a Signal with a recycled object of type _pooType. + */ + public var onRecycle:Signal; + /** + * dispatches a Signal with an object of type _pooType before its destruction. + */ + public var onDestroy:Signal; + + // Start of the list of free objects + protected var _freeListHead:DoublyLinkedListNode = null; + protected var _freeCount:uint = 0; + + protected var gc:Vector.; + + /** + * An implementation of an object Pool to limit instantiation for better performances. + * Though you pass the Class as a parameter at the pool creation, there's no way for it to send you back your object correctly typed + * If you want that, reimplement the Pool class and the DoublyLinkedListNode for each of your pool or port these files to Haxe with Generics ! + * WARNING : Be sure to design your pooled objects with NO constructor parameters and an 'init' method of some kind that will reinitialized + * all necessary properties each time your objects are 'recycled'. + * WARNING : Remember to cast your objects in the correct type before using them each time you get one from a DoublyLinkedListNode.data !!! + * + * @param pooledType the Class Object of the type you want to store in this pool + * @param defaultParams default params applied to newly created objects (important for physics) + * @param poolGrowthRate the number of object to instantiate each time a new one is needed and the free list is empty + * @param isCitrusObjectPool a boolean, set it to true if the Pool is composed of Physics/CitrusSprite, set it to false for SpriteArt/StarlingArt + */ + public function PoolObject(pooledType:Class,defaultParams:Object, poolGrowthRate:uint, isCitrusObjectPool:Boolean):void { + + super(); + + _poolType = pooledType; + _defaultParams = defaultParams; + _poolGrowthRate = poolGrowthRate; + _isCitrusObjectPool = isCitrusObjectPool; + + onCreate = new Signal(_poolType); + onDispose = new Signal(_poolType); + onRecycle = new Signal(_poolType); + onDestroy = new Signal(_poolType); + + gc = new Vector.; + + } + + /** + * Call initializePool to create a pool of size _poolSize. + * all objects will instantly be created and disposed, ready to be recycled with get(). + * you have the option of not initializing the pool in which case the first get will return a new object + * and will grow the pool size according to the growth rate. + */ + public function initializePool(poolSize:uint = 1):void + { + _poolSize = poolSize; + increasePoolSize(_poolSize); + } + + /** + * Create new objects of the _poolType type and dispose them instantly in the free list for future needs. + * Called once at the pool creation with _poolSize as a parameter, and once with _poolGrowthRate + * each time a new Object is needed and the free list is empty. + * + * @param sizeIncrease the number of objects to instantiate and store in the free list + */ + protected function increasePoolSize(sizeIncrease:uint,params:Object = null):void { + + params = mergeParams(params); + + for (var i:int = 0; i < sizeIncrease; ++i) { + var node:DoublyLinkedListNode = new DoublyLinkedListNode(); + + _create(node, params); + _dispose(node); + + if (_freeListHead) { + _freeListHead.prev = node; + node.next = _freeListHead; + _freeListHead = node; + } else { + _freeListHead = node; + } + + ++_freeCount; + + } + + } + + /** Get an object from the free list and returns the node holding it in its data property. + * It will be reinitialize inside this function. You may need to cast it. + * @param params It calls an initialize method. If the pool _isCitrusObjectPool is true, it calls the CitrusObject initialize method. + * @return A node holding the newly 'recycled' object + */ + public function get(params:Object = null):DoublyLinkedListNode { + + var node:DoublyLinkedListNode; + + // if no object is available in the freelist, make some more ! + if (!_freeListHead) increasePoolSize(_poolGrowthRate,params); + + // get the first free object + node = _freeListHead; + + // extract it from the free list + if (node.next) { + _freeListHead = node.next; + _freeListHead.prev = null; + node.next = null; + } else + _freeListHead = null; + + + // append it to the list of the pool + if (head != null) { + tail.next = node; + node.prev = tail; + tail = node; + } else + head = tail = node; + + ++_count; + --_freeCount; + + _recycle(node, params); + + return node; + } + + /** + * override to create your custom pooled object differently. + * @param node + * @param params + */ + protected function _create(node:DoublyLinkedListNode, params:Object = null):void { + onCreate.dispatch((node.data as _poolType),params); + } + + /** + * override to recycle your custom pooled object differently. + * @param node + * @param params + */ + protected function _recycle(node:DoublyLinkedListNode, params:Object = null):void { + onRecycle.dispatch((node.data as _poolType),params) + } + + /** + * override to dispose your custom pooled object differently. + * @param node + * @param params + */ + protected function _dispose(node:DoublyLinkedListNode):void { + onDispose.dispatch((node.data as _poolType)); + (node.data as _poolType).kill = false; + } + + /** + * override to destroy your custom pooled object differently. + * @param node + * @param params + */ + protected function _destroy(node:DoublyLinkedListNode):void { + onDestroy.dispatch((node.data as _poolType)); + } + + /** + * returns a new params object where newParams adds or overwrites parameters to the default params object defined in the constructor. + * @param newParams + * @return Object + */ + protected function mergeParams(newParams:Object):Object + { + var p:Object = {}; + var k:String; + + for (k in _defaultParams) + p[k] = _defaultParams[k]; + + for (k in newParams) + p[k] = newParams[k]; + + return p; + } + + public function updatePhysics(timeDelta:Number):void { + + var tmpHead:DoublyLinkedListNode = head; + + while (tmpHead != null) { + if((tmpHead.data as _poolType).updateCallEnabled) + (tmpHead.data as _poolType).update(timeDelta); + + //since updatePhysics is always called, we can dispose objects set to kill here. + if ("kill" in (tmpHead.data as _poolType) && (tmpHead.data as _poolType).kill) + gc.push(tmpHead); + tmpHead = tmpHead.next; + } + + if (gc && gc.length > 0) + { + for each(tmpHead in gc) + disposeFromData(tmpHead.data); + gc.length = 0; + } + + } + + /** + * check if object is free + * @param data + * @return + */ + public function isDataDisposed(data:*):Boolean + { + var tmpHead:DoublyLinkedListNode = _freeListHead; + while (tmpHead != null) { + if (tmpHead.data == data) + return true; + tmpHead = tmpHead.next; + } + return false; + } + + public function updateArt(stateView:ACitrusView):void { + + var tmpHead:DoublyLinkedListNode = head; + + while (tmpHead != null) { + (tmpHead.data as _poolType).update(stateView); + tmpHead = tmpHead.next; + } + + } + + /** Get a node from its data + * @param data node's data + * @return the node + */ + public function getNodeFromData(data:*):DoublyLinkedListNode { + + var tmpHead:DoublyLinkedListNode = head; + while (tmpHead != null) { + if (tmpHead.data == data) + return tmpHead; + + tmpHead = tmpHead.next; + } + + return null; + } + + /** + * Discard a now useless object to be stored in the free list. + * @param node the node holding the object to discard + */ + public function disposeNode(node:DoublyLinkedListNode):DoublyLinkedListNode { + + // Extract the node from the list + if (node == head) { + head = node.next; + if (head != null) head.prev = null; + } else { + node.prev.next = node.next; + } + + if (node == tail) { + tail = node.prev; + if (tail != null) tail.next = null; + } else { + node.next.prev = node.prev; + } + + node.prev = null; + + // Store the discarded object in the free list + if (_freeListHead) { + _freeListHead.prev = node; + node.next = _freeListHead; + _freeListHead = node; + } else { + _freeListHead = node; + node.next = null; + } + + --_count; + ++_freeCount; + + _dispose(node); + + return node; + } + + /** + * dispose of data object to the pool. + * @param data + */ + public function disposeFromData(data:*):DoublyLinkedListNode + { + var n:DoublyLinkedListNode = getNodeFromData(data as _poolType); + if(n) + return disposeNode(n); + else + throw new Error("This data is already disposed :",data); + } + + /** + * Discard all currently used objects and send them all back to the free list + */ + public function disposeAll():void { + while (head) { + disposeNode(head); + } + } + + public function killAll():void + { + var node:DoublyLinkedListNode = head; + while (node) { + (node.data as CitrusObject).kill = true; + node = node.next; + } + } + + /** + * loops through all disposed nodes and applies callback (only free objects will be affected) + * @param callback gets node.data for argument. + */ + public function foreachDisposed(callback:Function):Boolean + { + var node:DoublyLinkedListNode = _freeListHead; + while (node) { + if (callback(node.data as _poolType)) + return true; + node = node.next; + } + return false; + } + + /** + * loops through all recycled nodes and applies callback (only objects currently in use will be affected) + * @param callback gets node.data for argument. + */ + public function foreachRecycled(callback:Function):Boolean + { + var node:DoublyLinkedListNode = head; + while (node) { + if (callback(node.data as _poolType)) + return true; + node = node.next; + } + return false; + } + + /** + * loops through all nodes and applies callback (both recycled and free objects will be affected) + * @param callback gets node.data for argument. + */ + public function foreach(callback:Function):Boolean + { + var node:DoublyLinkedListNode = head; + while (node) { + if (callback(node.data as _poolType)) + return true; + node = node.next; + } + node = _freeListHead; + while (node) { + if (callback(node.data as _poolType)) + return true; + node = node.next; + } + return false; + } + + /** + * Completely destroy all the content of the pool (the free objects) + * and "unlink" from recycled object. (called automatically by the state) + */ + public function clear():void { + + disposeAll(); + + var node:DoublyLinkedListNode; + + while (_freeListHead) { + node = _freeListHead; + + _destroy(node); + + node.data = null; + + _freeListHead = node.next; + if (_freeListHead) _freeListHead.prev = null; + node.next = null; + + _freeCount--; + } + + _freeListHead = null; + head = null; + + } + + /** + * after clearing, just get rid of signals etc... + */ + public function destroy():void + { + clear(); + + onCreate.removeAll(); + onDestroy.removeAll(); + onDispose.removeAll(); + onRecycle.removeAll(); + + _defaultParams = null; + + gc.length = 0; + gc = null; + } + + /** + * returns the amount of objects currently in use. + */ + override public function get length():uint + { + return _count; + } + + /** + * returns the amount of objects currently in use. + */ + public function get recycledSize():uint + { + return _count; + } + + /** + * returns the amount of free objects. + */ + public function get poolSize():uint + { + return _freeCount; + } + + /** + * returns the amount of free objects and objects in use. + */ + public function get allSize():uint + { + return _freeCount + _count; + } + + /** + * return true if the Pool is composed of Physics/CitrusSprite, false for SpriteArt/StarlingArt + */ + public function get isCitrusObjectPool():Boolean { + return _isCitrusObjectPool; + } + + } +} \ No newline at end of file diff --git a/src/citrus/datastructures/Tools.as b/src/citrus/datastructures/Tools.as new file mode 100644 index 00000000..13390a33 --- /dev/null +++ b/src/citrus/datastructures/Tools.as @@ -0,0 +1,71 @@ +package citrus.datastructures { + + public class Tools { + + /** + * An equivalent of PHP's recursive print function print_r, which displays objects and arrays in a way that's readable by humans. Made by base 86. + * @param obj Object to be printed. + * @param level (Optional) Current recursivity level, used for recursive calls. + * @param output (Optional) The output, used for recursive calls. + */ + public static function pr(obj:*, level:int = 0, output:String = ''):* { + if (level == 0) + output = '(' + Tools.typeOf(obj) + ') {\n'; + else if + (level == 10) return output; + + var tabs:String = '\t'; + for (var i:int = 0; i < level; i++, tabs += '\t') { + } + for (var child:* in obj) { + output += tabs + '[' + child + '] => (' + Tools.typeOf(obj[child]) + ') '; + + if (Tools.count(obj[child]) == 0) + output += obj[child]; + + var childOutput:String = ''; + if (typeof obj[child] != 'xml') { + childOutput = Tools.pr(obj[child], level + 1); + } + if (childOutput != '') { + output += '{\n' + childOutput + tabs + '}'; + } + output += '\n'; + } + + if (level == 0) + trace(output + '}\n'); + else + return output; + } + + /** + * An extended version of the 'typeof' function. + * @param variable + * @return Returns the type of the variable. + */ + public static function typeOf(variable:*):String { + if (variable is Array) return 'array'; + else if (variable is Date) return 'date'; + else return typeof variable; + } + + /** + * Returns the size of an object. + * @param obj Object to be counted. + */ + public static function count(obj:Object):uint { + + if (Tools.typeOf(obj) == 'array') + return obj.length; + else { + var len:uint = 0; + for (var item:* in obj) { + if (item != 'mx_internal_uid') + len++; + } + return len; + } + } + } +} \ No newline at end of file diff --git a/src/citrus/events/CitrusEvent.as b/src/citrus/events/CitrusEvent.as new file mode 100644 index 00000000..403f518f --- /dev/null +++ b/src/citrus/events/CitrusEvent.as @@ -0,0 +1,52 @@ +package citrus.events +{ + public class CitrusEvent + { + internal var _type:String; + internal var _phase:int = CAPTURE_PHASE; + internal var _bubbles:Boolean = true; + internal var _cancelable:Boolean = false; + internal var _target:CitrusEventDispatcher; + internal var _currentTarget:CitrusEventDispatcher; + internal var _currentListener:Function; + + public function CitrusEvent(type:String, bubbles:Boolean = true, cancelable:Boolean = false) + { + _type = type; + _bubbles = bubbles; + _cancelable = cancelable; + } + + protected function setTarget(object:*):void + { + _target = object; + } + + public function clone():CitrusEvent + { + var e:CitrusEvent = new CitrusEvent(_type,_bubbles,_cancelable); + e._target = e._currentTarget = _currentTarget; + return e; + } + + public function get type():String { return _type;} + public function get phase():int { return _phase;} + public function get bubbles():Boolean { return _bubbles;} + public function get cancelable():Boolean { return _cancelable;} + public function get target():CitrusEventDispatcher { return _target;} + public function get currentTarget():CitrusEventDispatcher { return _currentTarget;} + public function get currentListener():Function { return _currentListener;} + + public function toString():String + { + return "[CitrusEvent type:" + _type + " target:" + Object(_target).constructor +" currentTarget:"+ Object(_currentTarget).constructor +" phase:" + _phase + " bubbles:" + _bubbles +" cancelable:" + _cancelable + " ]"; + } + + public static var CAPTURE_PHASE:int = 0; + public static var AT_TARGET:int = 1; + public static var BUBBLE_PHASE:int = 2; + + + } + +} \ No newline at end of file diff --git a/src/citrus/events/CitrusEventDispatcher.as b/src/citrus/events/CitrusEventDispatcher.as new file mode 100644 index 00000000..1e103c64 --- /dev/null +++ b/src/citrus/events/CitrusEventDispatcher.as @@ -0,0 +1,187 @@ +package citrus.events +{ + import flash.utils.Dictionary; + import citrus.core.citrus_internal; + + /** + * experimental event dispatcher (wip) + * TODO: + * - check consistency of bubbling/capturing + * - propagation stop ? + */ + + public class CitrusEventDispatcher + { + use namespace citrus_internal; + + protected var listeners:Dictionary; + + protected var dispatchParent:CitrusEventDispatcher; + protected var dispatchChildren:Vector.; + + public function CitrusEventDispatcher() + { + listeners = new Dictionary(); + } + + citrus_internal function addDispatchChild(child:CitrusEventDispatcher):CitrusEventDispatcher + { + if (!dispatchChildren) + dispatchChildren = new Vector.(); + + child.dispatchParent = this; + dispatchChildren.push(child); + return child; + } + + citrus_internal function removeDispatchChild(child:CitrusEventDispatcher):void + { + var index:int = dispatchChildren.indexOf(child); + if (index < 0) + return; + child.dispatchParent = null; + dispatchChildren.splice(index, 1); + + if (dispatchChildren.length == 0) + dispatchChildren = null; + } + + citrus_internal function removeDispatchChildren():void + { + var child:CitrusEventDispatcher; + for each(child in dispatchChildren) + removeDispatchChild(child); + } + + /** + * Warning: all references to the listener will be strong and you need to remove them explicitly. + */ + public function addEventListener(type:String, listener:Function, useCapture:Boolean = false):void + { + if (type in listeners) + listeners[type].push({func:listener,useCapture:useCapture}); + else + { + listeners[type] = new Vector.(); + listeners[type].push({func:listener,useCapture:useCapture}); + } + + } + + public function removeEventListener(type:String, listener:Function):void + { + if (type in listeners) + { + var index:String; + var list:Vector. = listeners[type]; + for (index in list) + if (list[index].func == listener) + list.splice(int(index), 1) + } + } + + public function willTrigger(func:Function):Boolean + { + var i:String; + var list:Vector.; + var o:Object; + for (i in listeners) + { + list = listeners[i]; + for each(o in list) + if (o.func == func) + return true; + } + return false; + } + + public function dispatchEvent(event:CitrusEvent):void + { + if (!event._target) + event._target = this; + + event._currentTarget = this; + + var phase:int = event._phase; + var foundTarget:Boolean = false; + + if (this == event._target) + event._phase = CitrusEvent.AT_TARGET; + + var o:Object; + if (event._type in listeners) + { + var list:Vector. = listeners[event.type]; + for each(o in list) + { + event._currentListener = o.func; + + if (o.func.length == 0) + o.func.apply(); + else + o.func.apply(null, [event]); + + foundTarget = true; + } + } + + if (event._phase == CitrusEvent.AT_TARGET && event._bubbles) + phase = event._phase = CitrusEvent.BUBBLE_PHASE; + + if (dispatchChildren && phase == CitrusEvent.CAPTURE_PHASE) + { + var child:CitrusEventDispatcher; + for each(child in dispatchChildren) + { + child.dispatchEvent(event); + } + } + + if (dispatchParent && phase == CitrusEvent.BUBBLE_PHASE) + { + dispatchParent.dispatchEvent(event); + } + } + + public function hasEventListener(type:String):Boolean + { + return type in listeners; + } + + /** + * remove all listeners of event + */ + public function removeListenersOf(type:String):void + { + if (type in listeners) + delete listeners[type]; + } + + /** + * remove listener from all events + */ + public function removeListener(listener:Function):void + { + var i:String; + var j:String; + var list:Vector.; + for (i in listeners) + { + list = listeners[i]; + for (j in list) + if (listener == list[j].func) + list.splice(int(j), 1); + } + } + + /** + * remove all event listeners (clears lists) + */ + public function removeEventListeners():void + { + listeners = new Dictionary(); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/events/CitrusSoundEvent.as b/src/citrus/events/CitrusSoundEvent.as new file mode 100644 index 00000000..d9e3e818 --- /dev/null +++ b/src/citrus/events/CitrusSoundEvent.as @@ -0,0 +1,110 @@ +package citrus.events +{ + import citrus.events.CitrusEvent; + import citrus.sounds.CitrusSound; + import citrus.sounds.CitrusSoundInstance; + + public class CitrusSoundEvent extends CitrusEvent + { + + /** + * CitrusSound related events + */ + public static const SOUND_ERROR:String = "SOUND_ERROR"; + public static const SOUND_LOADED:String = "SOUND_LOADED"; + public static const ALL_SOUNDS_LOADED:String = "ALL_SOUNDS_LOADED"; + + /** + * CitrusSoundInstance related events + */ + + /** + * dispatched when a sound instance starts playing + */ + public static const SOUND_START:String = "SOUND_START"; + /** + * dispatched when a sound instance pauses + */ + public static const SOUND_PAUSE:String = "SOUND_PAUSE"; + /** + * dispatched when a sound instance resumes + */ + public static const SOUND_RESUME:String = "SOUND_RESUME"; + /** + * dispatched when a sound instance loops (not when it loops indifinately) + */ + public static const SOUND_LOOP:String = "SOUND_LOOP"; + /** + * dispatched when a sound instance ends + */ + public static const SOUND_END:String = "SOUND_END"; + /** + * dispatched when no sound channels are available for a sound instance to start + */ + public static const NO_CHANNEL_AVAILABLE:String = "NO_CHANNEL_AVAILABLE"; + /** + * dispatched when a non permanent sound instance is forced to stop + * to leave room for a new one. + */ + public static const FORCE_STOP:String = "FORCE_STOP"; + /** + * dispatched when a sound instance tries to play but CitrusSound is not ready + */ + public static const SOUND_NOT_READY:String = "SOUND_NOT_READY"; + + /** + * dispatched on any CitrusSoundEvent + */ + public static const EVENT:String = "EVENT"; + + public var soundName:String; + public var soundID:int; + public var sound:CitrusSound; + public var soundInstance:CitrusSoundInstance; + public var loops:int = 0; + public var loopCount:int = 0; + public var loadedRatio:Number; + public var loaded:Boolean; + public var error:Boolean; + + public function CitrusSoundEvent(type:String, sound:CitrusSound, soundinstance:CitrusSoundInstance,soundID:int = -1, bubbles:Boolean = true, cancelable:Boolean = false) + { + super(type, bubbles, cancelable); + + if (sound) + { + this.sound = sound; + soundName = sound.name; + loadedRatio = sound.loadedRatio; + loaded = sound.loaded; + error = sound.ioerror; + } + + if (soundinstance) + { + this.soundInstance = soundinstance; + loops = soundinstance.loops; + loopCount = soundinstance.loopCount; + } + + this.soundID = soundID; + + if(type == SOUND_ERROR || type == SOUND_LOADED || type == ALL_SOUNDS_LOADED) + setTarget(sound); + else + setTarget(soundinstance); + } + + override public function clone():CitrusEvent + { + return new CitrusSoundEvent(type,sound,soundInstance,soundID,bubbles,cancelable) as CitrusEvent; + } + + override public function toString():String + { + return "[CitrusSoundEvent type: " + type + " sound: \"" + soundName + "\" ID: " + soundID + " loopCount: " + loopCount + " loops: " + loops + " ]"; + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/Input.as b/src/citrus/input/Input.as new file mode 100644 index 00000000..ede23c5d --- /dev/null +++ b/src/citrus/input/Input.as @@ -0,0 +1,435 @@ +package citrus.input { + + import citrus.core.citrus_internal; + import citrus.core.CitrusEngine; + import citrus.input.controllers.Keyboard; + + import org.osflash.signals.Signal; + + /** + * A class managing input of any controllers that is an InputController. + * Actions are inspired by Midi signals, but they carry an InputAction object. + * "action signals" are either ON, OFF, or CHANGE. + * to track action status, and check whether action was just triggered or is still on, + * actions have phases (see InputAction). + **/ + public class Input + { + protected var _ce:CitrusEngine; + protected var _timeActive:int = 0; + protected var _enabled:Boolean = true; + protected var _initialized:Boolean; + + protected var _controllers:Vector.; + protected var _actions:Vector.; + + /** + * time interval to clear the InputAction's disposed list automatically. + */ + public var clearDisposedActionsInterval:uint = 480; + + /** + * Lets InputControllers trigger actions. + */ + public var triggersEnabled:Boolean = true; + + protected var _routeActions:Boolean = false; + protected var _routeChannel:uint; + + internal var actionON:Signal; + internal var actionOFF:Signal; + internal var actionCHANGE:Signal; + + //easy access to the default keyboard + public var keyboard:Keyboard; + + public function Input() + { + _controllers = new Vector.(); + _actions = new Vector.(); + + actionON = new Signal(InputAction); + actionOFF = new Signal(InputAction); + actionCHANGE = new Signal(InputAction); + + actionON.add(doActionON); + actionOFF.add(doActionOFF); + actionCHANGE.add(doActionCHANGE); + + _ce = CitrusEngine.getInstance(); + } + + public function initialize():void + { + if (_initialized) + return; + + //default keyboard + keyboard = new Keyboard("keyboard"); + + _initialized = true; + } + + public function addController(controller:InputController):void + { + if (_controllers.lastIndexOf(controller) < 0) + _controllers.push(controller); + } + + public function addAction(action:InputAction):void + { + if (_actions.lastIndexOf(action) < 0) + _actions[_actions.length] = action; + } + + public function controllerExists(name:String):Boolean + { + for each (var c:InputController in _controllers) + { + if (name == c.name) + return true; + } + return false; + } + + public function getControllerByName(name:String):InputController + { + var c:InputController; + for each (c in _controllers) + if (name == c.name) + return c; + return null; + } + + /** + * Returns the corresponding InputAction object if it has been triggered OFF in this frame or in the previous frame, + * or null. + */ + public function hasDone(actionName:String, channel:int = -1):InputAction + { + var a:InputAction; + for each (a in _actions) + if (a.name == actionName && (channel > -1 ? (_routeActions ? (_routeChannel == channel) : a.channel == channel) : true ) && a.phase == InputPhase.END) + return a; + return null; + } + + /** + * Returns the corresponding InputAction object if it has been triggered on the previous frame or is still going, + * or null. + */ + public function isDoing(actionName:String, channel:int = -1):InputAction + { + var a:InputAction; + for each (a in _actions) + if (a.name == actionName && (channel > -1 ? (_routeActions ? (_routeChannel == channel) : a.channel == channel) : true ) && a.time > 1 && a.phase < InputPhase.END) + return a; + return null; + } + + /** + * Returns the corresponding InputAction object if it has been triggered on the previous frame. + */ + public function justDid(actionName:String, channel:int = -1):InputAction + { + var a:InputAction; + for each (a in _actions) + if (a.name == actionName && (channel > -1 ? (_routeActions ? (_routeChannel == channel) : a.channel == channel) : true ) && a.time == 1) + return a; + return null; + } + + /** + * get an action by name from the current 'active' actions , optionnally filtered by channel, controller or phase. + * returns null if no actions are found. + * + * example : + * + * var action:InputAction = _ce.input.getAction("jump",-1,null,InputPhase.ON); + * if(action && action.time > 120) + * trace("the jump action lasted for more than 120 frames. its value is",action.value); + * + * + * keep doing the jump action for about 2 seconds (if running at 60 fps) and you'll see the trace. + * @param name + * @param channel -1 to include all channels. + * @param controller null to include all controllers. + * @param phase -1 to include all phases. + * @return InputAction + */ + public function getAction(name:String, channel:int = -1, controller:InputController = null, phase:int = - 1):InputAction + { + var a:InputAction; + for each (a in _actions) + if (name == a.name && (channel == -1 ? true : (_routeActions ? (_routeChannel == channel) : a.channel == channel) ) && (controller != null ? a.controller == controller : true ) && (phase == -1 ? true : a.phase == phase ) ) + return a; + return null; + } + + /** + * Returns a list of active actions, optionnally filtered by channel, controller or phase. + * return an empty Vector.<InputAction> if no actions are found. + * + * @param channel -1 to include all channels. + * @param controller null to include all controllers. + * @param phase -1 to include all phases. + * @return + */ + public function getActions(channel:int = -1, controller:InputController = null, phase:int = - 1):Vector. + { + var actions:Vector. = new Vector.; + var a:InputAction; + for each (a in _actions) + if ( (channel == -1 ? true : (_routeActions ? (_routeChannel == channel) : a.channel == channel)) && (controller != null ? a.controller == controller : true ) && (phase == -1 ? true : a.phase == phase ) ) + actions.push(a) + return actions; + } + + /** + * Adds a new action of phase 0 if it does not exist. + */ + internal function doActionON(action:InputAction):void + { + if (!triggersEnabled) + { + action.dispose(); + return; + } + var a:InputAction; + + for each (a in _actions) + if (a.eq(action)) + { + a._phase = InputPhase.BEGIN; + action.dispose(); + return; + } + action._phase = InputPhase.BEGIN; + _actions[_actions.length] = action; + } + + /** + * Sets action to phase 3. will be advanced to phase 4 in next update, and finally will be removed + * on the update after that. + */ + internal function doActionOFF(action:InputAction):void + { + if (!triggersEnabled) + { + action.dispose(); + return; + } + var a:InputAction; + for each (a in _actions) + if (a.eq(action)) + { + a._phase = InputPhase.END; + a._value = action._value; + a._message = action._message; + action.dispose(); + return; + } + } + + /** + * Changes the value property of an action, or adds action to list if it doesn't exist. + * a continuous controller, can simply trigger ActionCHANGE and never have to trigger ActionON. + * this will take care adding the new action to the list, setting its phase to 0 so it will respond + * to justDid, and then only the value will be changed. - however your continous controller DOES have + * to end the action by triggering ActionOFF. + */ + internal function doActionCHANGE(action:InputAction):void + { + if (!triggersEnabled) + { + action.dispose(); + return; + } + var a:InputAction; + for each (a in _actions) + { + if (a.eq(action)) + { + a._phase = InputPhase.ON; + a._value = action._value; + a._message = action._message; + action.dispose(); + return; + } + } + action._phase = InputPhase.BEGIN; + _actions[_actions.length] = action; + } + + /** + * Input.update is called in the end of your state update. + * keep this in mind while you create new controllers - it acts only after everything else. + * update first updates all registered controllers then finally + * advances actions phases by one if not phase 2 (phase two can only be voluntarily advanced by + * doActionOFF.) and removes actions of phase 4 (this happens one frame after doActionOFF was called.) + */ + citrus_internal function update():void + { + if (InputAction.disposed.length > 0 && _timeActive % clearDisposedActionsInterval == 0) + InputAction.clearDisposed(); + _timeActive++; + + if (!_enabled) + return; + + var c:InputController; + for each (c in _controllers) + { + if (c.updateEnabled && c.enabled) + c.update(); + } + + var i:String; + for (i in _actions) + { + InputAction(_actions[i]).itime++; + if (_actions[i].phase > InputPhase.END) + { + _actions[i].dispose(); + _actions.splice(uint(i), 1); + } + else if (_actions[i].phase !== InputPhase.ON) + _actions[i]._phase++; + } + + + } + + public function removeController(controller:InputController):void + { + var i:int = _controllers.lastIndexOf(controller); + if(i < 0) + return; + stopActionsOf(controller); + _controllers.splice(i, 1); + } + + public function stopActionsOf(controller:InputController,channel:int = -1):void + { + var action:InputAction; + for each(action in _actions) + { + if (action.controller != controller) + continue; + + if (channel > -1) + { + if (action.channel == channel) + action._phase = InputPhase.ENDED; + } + else + action._phase = InputPhase.ENDED; + } + } + + public function resetActions():void + { + _actions.length = 0; + } + + /** + * addOrSetAction sets existing parameters of an action to new values or adds action if + * it doesn't exist. + */ + public function addOrSetAction(action:InputAction):void + { + var a:InputAction; + for each (a in _actions) + { + if (a.eq(action)) + { + a._phase = action.phase; + a._value = action.value; + return; + } + } + _actions[_actions.length] = action; + } + + /** + * returns a Vector of all actions in current frame. + * actions are cloned (no longer active inside the input system) + * as opposed to using getActions(). + */ + public function getActionsSnapshot():Vector. + { + var snapshot:Vector. = new Vector.; + var a:InputAction; + for each (a in _actions) + snapshot.push(a.clone()); + return snapshot; + } + + /** + * Start routing all actions to a single channel - used for pause menus or generally overriding the Input system. + */ + public function startRouting(channel:uint):void + { + _routeActions = true; + _routeChannel = channel; + } + + /** + * Stop routing actions. + */ + public function stopRouting():void + { + _routeActions = false; + _routeChannel = 0; + } + + /** + * Helps knowing if Input is routing actions or not. + */ + public function isRouting():Boolean + { + return _routeActions; + } + + public function get enabled():Boolean + { + return _enabled; + } + + public function set enabled(value:Boolean):void + { + if (_enabled == value) + return; + + var controller:InputController; + for each (controller in _controllers) + controller.enabled = value; + + _enabled = value; + } + + private function destroyControllers():void + { + for each (var c:InputController in _controllers) + { + c.destroy(); + } + _controllers.length = 0; + _actions.length = 0; + } + + public function destroy():void + { + destroyControllers(); + + actionON.removeAll(); + actionOFF.removeAll(); + actionCHANGE.removeAll(); + + resetActions(); + InputAction.clearDisposed(); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/InputAction.as b/src/citrus/input/InputAction.as new file mode 100644 index 00000000..34f584f5 --- /dev/null +++ b/src/citrus/input/InputAction.as @@ -0,0 +1,154 @@ +package citrus.input +{ + /** + * InputAction reinforces the Action object structure (and typing.) + * it contains static action phase constants as well as helpful comparators. + */ + public class InputAction + { + //read only action keys + private var _name:String; + private var _controller:InputController; + private var _channel:uint; + private var _time:uint = 0; + + internal var _value:Number; + internal var _message:String; + internal var _phase:uint; + + public function InputAction(name:String, controller:InputController, channel:uint = 0, value:Number = 0, message:String = null, phase:uint = 0, time:uint = 0) + { + _name = name; + _controller = controller; + _channel = channel; + + _value = value; + _message = message; + _phase = phase; + _time = time; + } + + /** + * Clones the action and returns a new InputAction instance with the same properties. + */ + public function clone():InputAction + { + return InputAction.create(_name, _controller,_channel , _value, _message, _phase, _time); + } + + /** + * comp is used to compare an action with another action without caring about which controller + * the actions came from. it is the most common form of action comparison. + */ + public function comp(action:InputAction):Boolean + { + return _name == action.name && _channel == action.channel; + } + + /** + * eq is almost a strict action comparator. It will not only compare names and channels + * but also which controller the actions came from. + */ + public function eq(action:InputAction):Boolean + { + return _name == action.name && _controller == action.controller && _channel == action.channel; + } + + public function toString():String + { + return "\n[ Action # name: " + _name + " channel: " + _channel + " value: " + _value + " phase: " + _phase + " controller: " + _controller + " time: " + _time + " ]"; + } + + public function get name():String { return _name; } + /** + * InputController that triggered this action + */ + public function get controller():InputController { return _controller; } + /** + * action channel id. + */ + public function get channel():uint { return _channel; } + /** + * time (in frames) the action has been 'running' in the Input system. + */ + public function get time():uint { return _time; } + + /** + * value the action carries + */ + public function get value():Number { return _value; } + + /** + * message the action carries + */ + public function get message():String { return _message; } + + /** + * action phase + */ + public function get phase():Number { return _phase; } + + /** + * internal utiliy to keep public time read only + */ + internal function get itime():uint { return _time; } + internal function set itime(val:uint):void { _time = val; } + + + + // ------------ InputAction Pooling + + /** + * list of disposed InputActions. automatically disposed when they end in Input.as + */ + internal static var disposed:Vector. = new Vector.(); + + /** + * creates an InputAction either from a disposed InputAction object or a new one. + */ + public static function create(name:String, controller:InputController, channel:uint = 0, value:Number = 0, message:String = null, phase:uint = 0, time:uint = 0):InputAction + { + if (disposed.length > 0) + return disposed.pop().setTo(name, controller, channel, value, message, phase, time); + else + return new InputAction(name,controller,channel,value,message,phase,time); + } + + /** + * clear the list of disposed InputActions. + */ + public static function clearDisposed():void + { + disposed.length = 0; + } + + /** + * set all InputActions's properties (internal for recycling) + */ + internal function setTo(name:String, controller:InputController, channel:uint = 0, value:Number = 0, message:String = null, phase:uint = 0, time:uint = 0):InputAction + { + _name = name; + _controller = controller; + _channel = channel; + _value = value; + _message = message; + _phase = phase; + _time = time; + return this; + } + + public function dispose():void + { + _controller = null; + + var a:InputAction; + for each(a in disposed) + if (this == a) + return; + + disposed.push(this); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/InputController.as b/src/citrus/input/InputController.as new file mode 100644 index 00000000..a23a575f --- /dev/null +++ b/src/citrus/input/InputController.as @@ -0,0 +1,189 @@ +package citrus.input { + + import citrus.core.CitrusEngine; + + /** + * InputController is the parent of all the controllers classes. It provides the same helper that CitrusObject class : + * it can be initialized with a params object, which can be created via an object parser/factory. + */ + public class InputController + { + public static var hideParamWarnings:Boolean = false; + + public var name:String; + + protected var _ce:CitrusEngine; + protected var _input:Input; + protected var _initialized:Boolean; + protected var _enabled:Boolean = true; + protected var _updateEnabled:Boolean = false; + protected var _defaultChannel:uint = 0; + + private var action:InputAction; + + public function InputController(name:String, params:Object = null) + { + this.name = name; + + setParams(params); + + _ce = CitrusEngine.getInstance(); + _input = _ce.input; + + _ce.input.addController(this); + } + + /** + * Override this function if you need your controller to update when CitrusEngine updates the Input instance. + */ + public function update():void + { + + } + + /** + * Will register the action to the Input system as an action with an InputPhase.BEGIN phase. + * @param name string that defines the action such as "jump" or "fly" + * @param value optional value for your action. + * @param message optional message for your action. + * @param channel optional channel for your action. (will be set to the defaultChannel if not set. + */ + protected function triggerON(name:String, value:Number = 0,message:String = null, channel:int = -1):InputAction + { + if (_enabled) + { + action = InputAction.create(name, this, (channel < 0)? defaultChannel : channel , value, message); + _input.actionON.dispatch(action); + return action; + } + return null; + } + + /** + * Will register the action to the Input system as an action with an InputPhase.END phase. + * @param name string that defines the action such as "jump" or "fly" + * @param value optional value for your action. + * @param message optional message for your action. + * @param channel optional channel for your action. (will be set to the defaultChannel if not set. + */ + protected function triggerOFF(name:String, value:Number = 0,message:String = null, channel:int = -1):InputAction + { + if (_enabled) + { + action = InputAction.create(name, this, (channel < 0)? defaultChannel : channel , value, message); + _input.actionOFF.dispatch(action); + return action; + } + return null; + } + + /** + * Will register the action to the Input system as an action with an InputPhase.BEGIN phase if its not yet in the + * actions list, otherwise it will update the existing action's value and set its phase back to InputPhase.ON. + * @param name string that defines the action such as "jump" or "fly" + * @param value optional value for your action. + * @param message optional message for your action. + * @param channel optional channel for your action. (will be set to the defaultChannel if not set. + */ + protected function triggerCHANGE(name:String, value:Number = 0,message:String = null, channel:int = -1):InputAction + { + if (_enabled) + { + action = InputAction.create(name, this, (channel < 0)? defaultChannel : channel , value, message); + _input.actionCHANGE.dispatch(action); + return action; + } + return null; + } + + /** + * Will register the action to the Input system as an action with an InputPhase.END phase if its not yet in the + * actions list as well as a time to 1 (so that it will be considered as already triggered. + * @param name string that defines the action such as "jump" or "fly" + * @param value optional value for your action. + * @param message optional message for your action. + * @param channel optional channel for your action. (will be set to the defaultChannel if not set. + */ + protected function triggerONCE(name:String, value:Number = 0, message:String = null, channel:int = -1):InputAction + { + if (_enabled) + { + action = InputAction.create(name, this, (channel < 0)? defaultChannel : channel , value, message, InputPhase.END); + _input.actionON.dispatch(action); + action = InputAction.create(name, this, (channel < 0)? defaultChannel : channel , value, message, InputPhase.END); + _input.actionOFF.dispatch(action); + return action; + } + return null; + } + + public function get defaultChannel():uint + { + return _defaultChannel; + } + + public function set defaultChannel(value:uint):void + { + if (value == _defaultChannel) + return; + + _input.stopActionsOf(this); + _defaultChannel = value; + } + + public function get enabled():Boolean + { + return _enabled; + } + + public function set enabled(val:Boolean):void + { + _enabled = val; + } + + public function get updateEnabled():Boolean + { + return _updateEnabled; + } + + /** + * Removes this controller from Input. + */ + public function destroy():void + { + _input.removeController(this); + action = null; + } + + public function toString():String + { + return name; + } + + protected function setParams(object:Object):void + { + for (var param:String in object) + { + try + { + if (object[param] == "true") + this[param] = true; + else if (object[param] == "false") + this[param] = false; + else + this[param] = object[param]; + } + catch (e:Error) + { + if (!hideParamWarnings) + trace("Warning: The parameter " + param + " does not exist on " + this); + } + } + + _initialized = true; + + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/InputPhase.as b/src/citrus/input/InputPhase.as new file mode 100644 index 00000000..00d58576 --- /dev/null +++ b/src/citrus/input/InputPhase.as @@ -0,0 +1,37 @@ +package citrus.input +{ + public class InputPhase + { + + /** + * Action started in this frame. + * will be advanced to BEGAN on next frame. + */ + public static const BEGIN:uint = 0; + + /** + * Action started in previous frame and hasn't changed value. + * will be advanced to ON on next frame. + */ + public static const BEGAN:uint = 1; + + /** + * The "stable" phase, action began, its value may have been changed by the CHANGE signal. + * an action with this phase can only be advanced by an OFF signal, to phase END ; otherwise it stays in the system. + */ + public static const ON:uint = 2; + + /** + * Action has been triggered OFF in the current frame. + * will be advanced to ENDED on next frame. + */ + public static const END:uint = 3; + + /** + * Action has been triggered OFF in the previous frame, and will be disposed of in this frame. + */ + public static const ENDED:uint = 4; + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/AVirtualButton.as b/src/citrus/input/controllers/AVirtualButton.as new file mode 100644 index 00000000..c7ec1493 --- /dev/null +++ b/src/citrus/input/controllers/AVirtualButton.as @@ -0,0 +1,87 @@ +package citrus.input.controllers { + + import citrus.input.InputController; + + public class AVirtualButton extends InputController + { + //Common graphic properties + protected var _x:int; + protected var _y:int; + + protected var _margin:int = 130; + + protected var _visible:Boolean = true; + + protected var _buttonradius:int = 50; + + public var buttonAction:String = "button"; + public var buttonChannel:int = -1; + + public function AVirtualButton(name:String, params:Object = null) + { + super(name, params); + } + + /** + * Override this for specific drawing + */ + protected function initGraphics():void + { + trace("Warning: " + this + " does not render any graphics!"); + } + + public function set visible(value:Boolean):void + { + _visible = value; + } + + public function set buttonradius(value:int):void + { + if (!_initialized) + _buttonradius = value; + else + trace("Warning: You cannot set " + this + " buttonradius after it has been created. Please set it in the constructor."); + } + + public function set margin(value:int):void + { + if (!_initialized) + _margin = value; + else + trace("Warning: You cannot set " + this + " margin after it has been created. Please set it in the constructor."); + } + + public function get margin():int + { + return _margin; + } + + public function set x(value:int):void + { + if (!_initialized) + _x = value; + else + trace("Warning: you can only set " + this + " x through graphic.x after instanciation."); + } + + public function set y(value:int):void + { + if (!_initialized) + _y = value; + else + trace("Warning: you can only set " + this + " y through graphic.y after instanciation."); + } + + public function get visible():Boolean + { + return _visible; + } + + public function get buttonradius():int + { + return _buttonradius; + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/AVirtualJoystick.as b/src/citrus/input/controllers/AVirtualJoystick.as new file mode 100644 index 00000000..b5ac9c47 --- /dev/null +++ b/src/citrus/input/controllers/AVirtualJoystick.as @@ -0,0 +1,259 @@ +package citrus.input.controllers { + + import citrus.input.InputController; + import citrus.math.MathVector; + import flash.geom.Point; + import flash.geom.Rectangle; + + public class AVirtualJoystick extends InputController + { + //Common graphic properties + protected var _x:int; + protected var _y:int; + + protected var _realTouchPosition:Point = new Point(); + protected var _targetPosition:MathVector = new MathVector(); + + protected var _visible:Boolean = true; + + //joystick features + protected var _innerradius:int; + protected var _knobradius:int = 50; + protected var _radius:int = 130; + + //Axes values [-1;1] + protected var _xAxis:Number = 0; + protected var _yAxis:Number = 0; + + //Axes Actions + protected var _xAxisActions:Vector.; + protected var _yAxisActions:Vector.; + + protected var _grabbed:Boolean = false; + protected var _centered:Boolean = true; + + /** + * wether to restrict the knob's movement in a circle or in a square + * hint: square allows for extreme values on both axis when dragged in a corner. + */ + public var circularBounds:Boolean = true; + + /** + * alpha to use when the joystick is not active + */ + public var inactiveAlpha:Number = 0.3; + + /** + * alpha to use when the joystick is active (being dragged) + */ + public var activeAlpha:Number = 1; + + /** + * distance from the center at which no action will be fired. + */ + public var threshold:Number = 0.1; + + public function AVirtualJoystick(name:String, params:Object = null) + { + super(name, params); + } + + /** + * Override this for specific drawing + */ + protected function initGraphics():void + { + trace("Warning: " + this + " does not render any graphics!"); + } + + /** + * Set action ranges. + */ + protected function initActionRanges():void + { + _xAxisActions = new Vector.(); + _yAxisActions = new Vector.(); + + //register default actions to value intervals + + addAxisAction("x", "left", -1, -0.3); + addAxisAction("x", "right", 0.3, 1); + addAxisAction("y", "up", -1, -0.3); + addAxisAction("y", "down", 0.3, 1); + + addAxisAction("y", "duck", 0.8, 1); + addAxisAction("y", "jump", -1, -0.8); + + } + + public function removeAxisAction(axis:String, name:String):void + { + var actionlist:Vector.; + if (axis.toLowerCase() == "x") + actionlist = _xAxisActions; + else if (axis.toLowerCase() == "y") + actionlist = _yAxisActions; + else + throw(new Error("VirtualJoystick::removeAxisAction() invalid axis parameter (only x and y are accepted)")); + + var i:String; + for (i in actionlist) + if (actionlist[i].name == name) + actionlist.splice(uint(i),1); + } + + public function addAxisAction(axis:String, name:String, start:Number, end:Number):void + { + var actionlist:Vector.; + if (axis.toLowerCase() == "x") + actionlist = _xAxisActions; + else if (axis.toLowerCase() == "y") + actionlist = _yAxisActions; + else + throw(new Error("VirtualJoystick::addAxisAction() invalid axis parameter (only x and y are accepted)")); + + if ( (start < 0 && end > 0) || (start > 0 && end < 0) || start == end ) + throw(new Error("VirtualJoystick::addAxisAction() start and end values must have the same sign and not be equal")); + + if (!((start < -1 || start > 1) || (end < -1 || end > 1))) + actionlist.push({name: name, start: start, end: end}); + else + throw(new Error("VirtualJoystick::addAxisAction() start and end values must be between -1 and 1")); + } + + /** + * Give handleGrab the relative position of touch or mouse to knob. + * It will handle knob movement restriction, action triggering and set _knobX and _knobY for knob positioning. + */ + protected function handleGrab(relativeX:int, relativeY:int):void + { + if (circularBounds) + { + var dist:Number = relativeX*relativeX + relativeY*relativeY ; + if (dist <= _innerradius*_innerradius) + { + _targetPosition.setTo(relativeX, relativeY); + } + else + { + _targetPosition.setTo(relativeX, relativeY); + _targetPosition.length = _innerradius; + } + } + else + { + if (relativeX < _innerradius && relativeX > -_innerradius) + _targetPosition.x = relativeX; + else if (relativeX > _innerradius) + _targetPosition.x = _innerradius; + else if (relativeX < -_innerradius) + _targetPosition.x = -_innerradius; + + if (relativeY < _innerradius && relativeY > -_innerradius) + _targetPosition.y = relativeY; + else if (relativeY > _innerradius) + _targetPosition.y = _innerradius; + else if (relativeY < -_innerradius) + _targetPosition.y = -_innerradius; + } + + //normalize x and y axes value. + + _xAxis = _targetPosition.x / _innerradius; + _yAxis = _targetPosition.y / _innerradius; + + // Check registered actions on both axes + if (Math.sqrt((_xAxis * _xAxis) + (_yAxis * _yAxis)) <= threshold) + _input.stopActionsOf(this); + else + { + var a:Object; //action + var ratio:Number; + var val:Number; + + if (_xAxisActions.length > 0) + for each (a in _xAxisActions) + { + ratio = 1 / (a.end - a.start); + val = _xAxis <0 ? 1 - Math.abs((_xAxis - a.start)*ratio) : Math.abs((_xAxis - a.start) * ratio); + if ((_xAxis >= a.start) && (_xAxis <= a.end)) + triggerCHANGE(a.name, val); + else + triggerOFF(a.name, 0); + } + + if (_yAxisActions.length > 0) + for each (a in _yAxisActions) + { + ratio = 1 / (a.start - a.end); + val = _yAxis <0 ? Math.abs((_yAxis - a.end)*ratio) : 1 - Math.abs((_yAxis - a.end) * ratio); + if ((_yAxis >= a.start) && (_yAxis <= a.end)) + triggerCHANGE(a.name, val); + else + triggerOFF(a.name, 0); + } + + } + } + + protected function reset():void + { + _targetPosition.setTo(); + _xAxis = 0; + _yAxis = 0; + _input.stopActionsOf(this); + } + + public function set radius(value:int):void + { + if (!_initialized) + { + _radius = value; + _innerradius = _radius - _knobradius; + } + else + trace("Warning: You cannot set " + this + " radius after it has been created. Please set it in the constructor."); + } + + public function set knobradius(value:int):void + { + if (!_initialized) + { + _knobradius = value; + _innerradius = _radius - _knobradius; + } + else + trace("Warning: You cannot set " + this + " knobradius after it has been created. Please set it in the constructor."); + } + + public function set x(value:int):void + { + if (value == _x) + return; + + _x = value; + reset(); + } + + public function set y(value:int):void + { + if (value == _y) + return; + + _y = value; + reset(); + } + + public function get radius():int + { + return _radius; + } + + public function get knobradius():int + { + return _knobradius; + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/Accelerometer.as b/src/citrus/input/controllers/Accelerometer.as new file mode 100644 index 00000000..18b77117 --- /dev/null +++ b/src/citrus/input/controllers/Accelerometer.as @@ -0,0 +1,328 @@ +package citrus.input.controllers +{ + + import citrus.input.InputController; + + import flash.events.AccelerometerEvent; + import flash.geom.Vector3D; + import flash.sensors.Accelerometer; + import flash.utils.Dictionary; + + public class Accelerometer extends InputController + { + protected var _accel: flash.sensors.Accelerometer; + + //current accel + protected var _a:Vector3D = new Vector3D(); + //target accel + protected var _t:Vector3D = new Vector3D(); + + //rotation + protected var _rot:Vector3D = new Vector3D(); + //previous rotation + protected var _prevRot:Vector3D = new Vector3D(); + + //only start calculating when received first events from device. + protected var receivedFirstAccelUpdate:Boolean = false; + + /** + * Angle inside which no action is triggered, representing the "center" or the "idle position". + * the more this angle is big, the more the device needs to be rotated to start triggering actions. + */ + public var idleAngleZ:Number = Math.PI / 8; + + /** + * Angle inside which no action is triggered, representing the "center" or the "idle position". + * the more this angle is big, the more the device needs to be rotated to start triggering actions. + */ + public var idleAngleX:Number = Math.PI / 6; + + /** + * Set this to offset the Z rotation calculations : + */ + public var offsetZAngle:Number = 0; + + /** + * Set this to offset the Y rotation calculations : + */ + public var offsetYAngle:Number = 0; + + /** + * Set this to offset the X rotation calculations : + */ + public var offsetXAngle:Number = -Math.PI/2 + Math.PI/4; + + /** + * easing of the accelerometer's X value. + */ + public var easingX:Number = 0.5; + /** + * easing of the accelerometer's Y value. + */ + public var easingY:Number = 0.5; + /** + * easing of the accelerometer's Z value. + */ + public var easingZ:Number = 0.5; + + /** + * action name for the rotation on the X axis. + */ + public static const ROT_X:String = "rotX"; + /** + * action name for the rotation on the Y axis. + */ + public static const ROT_Y:String = "rotY"; + /** + * action name for the rotation on the Z axis. + */ + public static const ROT_Z:String = "rotZ"; + + /** + * action name for the raw accelerometer X value. + */ + public static const RAW_X:String = "rawX"; + /** + * action name for the raw accelerometer Y value. + */ + public static const RAW_Y:String = "rawY"; + /** + * action name for the raw accelerometer Z value. + */ + public static const RAW_Z:String = "rawZ"; + + /** + * action name for the X angular velocity value. + */ + public static const VEL_X:String = "velX"; + /** + * action name for the Y angular velocity value. + */ + public static const VEL_Y:String = "velY"; + /** + * action name for the Z angular velocity value. + */ + public static const VEL_Z:String = "velZ"; + + /** + * send the new raw values on each frame. + */ + public var triggerRawValues:Boolean = false; + /** + * send the new rotation values on each frame in radian. + */ + public var triggerAxisRotation:Boolean = false; + + /** + * if true, on each update values will be computed to send custom Actions (such as left right up down by default) + */ + public var triggerActions:Boolean = false; + + /** + * if true, on each update values will be computed to send the angular velocity of the device + */ + public var triggerVelocity:Boolean = false; + + /** + * helps prevent too much calls to triggerON/OFF by keeping track of what action is on/off + */ + protected var actions:Dictionary; + + public function Accelerometer(name:String,params:Object = null) + { + super(name, params); + + _updateEnabled = true; + + if (! flash.sensors.Accelerometer.isSupported) + { + trace(this, "Accelerometer is not supported"); + enabled = false; + } + else + { + _accel = new flash.sensors.Accelerometer(); + _accel.addEventListener(AccelerometerEvent.UPDATE, onAccelerometerUpdate); + } + + } + + /* + * This updates the target values of acceleration which will be eased on each frame through the update function. + */ + public function onAccelerometerUpdate(e:AccelerometerEvent):void + { + _t.x = e.accelerationX; + _t.y = e.accelerationY; + _t.z = e.accelerationZ; + + receivedFirstAccelUpdate = true; + } + + override public function update():void + { + if (!receivedFirstAccelUpdate) + return; + + //ease values + _a.x += (_t.x -_a.x) * easingX; + _a.y += (_t.y -_a.y) * easingY; + _a.z += (_t.z -_a.z) * easingZ; + + _rot.x = Math.atan2(_a.y, _a.z) + offsetXAngle; + _rot.y = Math.atan2(_a.x, _a.z) + offsetYAngle; + _rot.z = Math.atan2(_a.x, _a.y) + offsetZAngle; + + if (triggerRawValues) + { + triggerCHANGE(RAW_X, _a.x); + triggerCHANGE(RAW_Y, _a.y); + triggerCHANGE(RAW_Z, _a.z); + } + + if (triggerAxisRotation) + { + triggerCHANGE(ROT_X, _rot.x); + triggerCHANGE(ROT_Y, _rot.y); + triggerCHANGE(ROT_Z, _rot.z); + } + + if (triggerVelocity) + { + triggerCHANGE(VEL_X, (_rot.x - _prevRot.x) * _ce.stage.frameRate); + triggerCHANGE(VEL_Y, (_rot.y - _prevRot.y) * _ce.stage.frameRate); + triggerCHANGE(VEL_Z, (_rot.z - _prevRot.z) * _ce.stage.frameRate); + } + + if (triggerActions) + customActions(); + + _prevRot.x = _rot.x; + _prevRot.y = _rot.y; + _prevRot.z = _rot.z; + + } + + /** + * Override this function to customize actions based on orientation + * by default, if triggerActions is set to true, customActions will be called + * in which default actions such as left/right/up/down will be triggered + * based on the actual rotation of the device: + * in landscape mode, pivoting the device to the right will trigger a right action for example. + * to make it available in portrait mode, the offsetZAngle can help rotate that calculation by 90° or more + * depeding on your screen orientation... + * + * this was mostly tested on a fixed landscape orientation setting. + */ + protected function customActions():void + { + if (!actions) + { + actions = new Dictionary(); + actions["left"] = false; + actions["right"] = false; + actions["down"] = false; + actions["up"] = false; + actions["jump"] = false; + } + + //in idle position on Z + if (_rot.z < idleAngleZ && _rot.z > - idleAngleZ) + { + if (actions.left) + { + triggerOFF("left", 0); + actions.left = false; + } + if (actions.right) + { + triggerOFF("right", 0); + actions.right = false; + } + } + else + { + //going right + if (_rot.z < 0 && _rot.z > -Math.PI/2) + { + if (!actions.right) + { + triggerON("right", 1); + actions.right = true; + } + } + + //going left + if (_rot.z > 0 && _rot.z < Math.PI / 2) + { + if (!actions.left) + { + triggerON("left", 1); + actions.left = true; + } + } + } + + //in idle position on X + if (_rot.x < idleAngleX && _rot.x > - idleAngleX) + { + if (actions.jump) + { + triggerOFF("jump", 0); + actions.jump = false; + } + if (actions.up) + { + triggerOFF("up", 0); + actions.up = false; + } + if (actions.down) + { + triggerOFF("down", 0); + actions.down = false; + } + } + else + { + //going up + if (_rot.x < 0 && _rot.x > -Math.PI/2) + { + if (!actions.jump) + { + triggerON("jump", 1); + actions.jump = true; + } + if (!actions.up) + { + triggerON("up", 1); + actions.up = true; + } + } + + //going down + if (_rot.x > 0 && _rot.x < Math.PI / 2) + { + if (!actions.down) + { + triggerON("down", 1); + actions.down = true; + } + } + } + + + } + + /* + * Acceleration Vector + */ + public function get acceleration():Vector3D { return _a; } + + /* + * Rotation Vector + */ + public function get rotation():Vector3D { return _rot; } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/Keyboard.as b/src/citrus/input/controllers/Keyboard.as new file mode 100644 index 00000000..e538695f --- /dev/null +++ b/src/citrus/input/controllers/Keyboard.as @@ -0,0 +1,456 @@ +package citrus.input.controllers { + import citrus.input.InputController; + + import org.osflash.signals.Signal; + + import flash.events.KeyboardEvent; + import flash.utils.Dictionary; + import flash.utils.describeType; + + /** + * The default Keyboard controller. + * A single key can trigger multiple actions, each of these can be sent to different channels. + * + * Keyboard holds static keycodes constants (see bottom). + */ + public class Keyboard extends InputController + { + protected var _keyActions:Dictionary; + + /** + * on native keyboard key up, dispatches keyCode and keyLocation as well as a 'vars' object which you can use to prevent default or stop immediate propagation of the native event. + * see the code below : + * + * + * public function onSoftKeys(keyCode:int,keyLocation:int,vars:Object):void + * { + * switch (keyCode) + * { + * case Keyboard.BACK: + * vars.prevent = true; + * trace("back button, default prevented."); + * break; + * case Keyboard.MENU: + * trace("menu"); + * break; + * case Keyboard.SEARCH: + * trace("search"); + * break; + * case Keyboard.ENTER: + * vars.stop = true; + * trace("enter, will not go through the input system because propagation was stopped."); + * break; + * } + * } + * + */ + public var onKeyUp:Signal; + + /** + * on native keyboard key down, dispatches keyCode and keyLocation as well as a 'vars' object which you can use to prevent default or stop immediate propagation of the native event. + * see the code below : + * + * + * public function onSoftKeys(keyCode:int,keyLocation:int,vars:Object):void + * { + * switch (keyCode) + * { + * case Keyboard.BACK: + * vars.prevent = true; + * trace("back button, default prevented."); + * break; + * case Keyboard.MENU: + * trace("menu"); + * break; + * case Keyboard.SEARCH: + * trace("search"); + * break; + * case Keyboard.ENTER: + * vars.stop = true; + * trace("enter, will not go through the input system because propagation was stopped."); + * break; + * } + * } + * + */ + public var onKeyDown:Signal; + + public var keyNames:Dictionary; + + public function Keyboard(name:String, params:Object = null) + { + super(name, params); + + _keyActions = new Dictionary(); + + //default arrow keys + space bar jump + + addKeyAction("left", LEFT); + addKeyAction("up", UP); + addKeyAction("right", RIGHT); + addKeyAction("down", DOWN); + addKeyAction("jump", SPACE); + + _ce.stage.addEventListener(KeyboardEvent.KEY_DOWN, handleKeyDown); + _ce.stage.addEventListener(KeyboardEvent.KEY_UP, handleKeyUp); + + onKeyUp = new Signal(uint,int,Object); + onKeyDown = new Signal(uint,int,Object); + + keyNames = new Dictionary(); + var xmlDesc:XMLList = describeType(Keyboard).child("constant"); + var constName:String; + var constVal:uint; + for each(var key:XML in xmlDesc) + { + constName = key.attribute("name"); + constVal = Keyboard[constName]; + + //don't register the azerty helper constants + if(constName.substr(0,7) == "AZERTY_") + continue; + + if(constVal is uint) + keyNames[constVal] = constName; + } + } + + private function handleKeyDown(e:KeyboardEvent):void + { + if (onKeyDown.numListeners > 0) + { + var vars:Object = { prevent:false, stop:false }; + onKeyDown.dispatch(e.keyCode, e.keyLocation, vars ); + if (vars.prevent) + e.preventDefault(); + if (vars.stop) + { + e.stopImmediatePropagation(); + return; + } + } + + if (_keyActions[e.keyCode]) + { + var a:Object; + for each (a in _keyActions[e.keyCode]) + { + triggerON(a.name, 1, null, (a.channel < 0 ) ? defaultChannel : a.channel); + } + } + } + + private function handleKeyUp(e:KeyboardEvent):void + { + if (onKeyUp.numListeners > 0) + { + var vars:Object = { prevent:false, stop:false }; + onKeyUp.dispatch(e.keyCode, e.keyLocation, vars ); + if (vars.prevent) + e.preventDefault(); + if (vars.stop) + { + e.stopImmediatePropagation(); + return; + } + } + + if (_keyActions[e.keyCode]) + { + var a:Object; + for each (a in _keyActions[e.keyCode]) + { + triggerOFF(a.name, 0, null, (a.channel < 0 ) ? defaultChannel : a.channel); + } + } + } + + /** + * Add an action to a Key if action doesn't exist on that Key. + */ + public function addKeyAction(actionName:String, keyCode:uint, channel:int = -1):void + { + if (!_keyActions[keyCode]) + _keyActions[keyCode] = new Vector.(); + else + { + var a:Object; + for each (a in _keyActions[keyCode]) + if (a.name == actionName && a.channel == channel) + return; + } + + /*if (channel < 0) + channel = defaultChannel;*/ + + _keyActions[keyCode].push({name: actionName, channel: channel}); + } + + /** + * Removes action from a key code, by name. + */ + public function removeActionFromKey(actionName:String, keyCode:uint):void + { + if (_keyActions[keyCode]) + { + var actions:Vector. = _keyActions[keyCode]; + var i:String; + for (i in actions) + if (actions[i].name == actionName) + { + triggerOFF(actionName); + actions.splice(uint(i), 1); + return; + } + } + } + + /** + * Removes every actions by name, on every keys. + */ + public function removeAction(actionName:String):void + { + var actions:Vector.; + var i:String; + for each (actions in _keyActions) + for (i in actions) + if (actions[uint(i)].name == actionName) + { + triggerOFF(actionName); + actions.splice(uint(i), 1); + } + } + + /** + * Deletes the entire registry of key actions. + */ + public function resetAllKeyActions():void + { + _keyActions = new Dictionary(); + _ce.input.stopActionsOf(this); + } + + /** + * Helps swap actions from a key to another key. + */ + public function changeKeyAction(previousKey:uint, newKey:uint):void + { + + var actions:Vector. = getActionsByKey(previousKey); + setKeyActions(newKey, actions); + removeKeyActions(previousKey); + } + + /** + * Sets all actions on a key + */ + private function setKeyActions(keyCode:uint, actions:Vector.):void + { + + if (!_keyActions[keyCode]) + _keyActions[keyCode] = actions; + _ce.input.stopActionsOf(this); + } + + /** + * Removes all actions on a key. + */ + public function removeKeyActions(keyCode:uint):void + { + delete _keyActions[keyCode]; + _ce.input.stopActionsOf(this); + } + + /** + * Returns all actions on a key in Vector format or returns null if none. + */ + public function getActionsByKey(keyCode:uint):Vector. + { + if (_keyActions[keyCode]) + return _keyActions[keyCode]; + else + return null; + } + + /** + * returns an array of all the names of the keys that will trigger the action. + * @param channel filter by channel number, if -1, all key/action/channel combinations are considered + */ + public function getKeysFromAction(actionName:String, channel:int = -1):Array + { + var arr:Array = []; + for(var k:String in _keyActions) + for each(var o:Object in _keyActions[uint(k)]) + if(o.name == actionName && ( channel > -1 ? o.channel > -1 ? o.channel == channel : true : true ) ) + arr.push(keyNames[uint(k)]); + + return arr; + } + + /** + * returns the name of the first found key that should trigger the action. + * @param channel filter by channel number, if -1, all key/action/channel combinations are considered + */ + public function getKeyFromAction(actionName:String, channel:int = -1):String + { + var result:Array = getKeysFromAction(actionName,channel); + if(result && result.length > 0) + return result[0]; + else + return null; + } + + override public function destroy():void + { + onKeyUp.removeAll(); + onKeyDown.removeAll(); + + _ce.stage.removeEventListener(KeyboardEvent.KEY_DOWN, handleKeyDown); + _ce.stage.removeEventListener(KeyboardEvent.KEY_UP, handleKeyUp); + + _keyActions = null; + + super.destroy(); + } + + /* + * KEYCODES + * they refer to the character written on a key (the first bottom left one if there are many). + * based on commonly used QWERTY keyboard. + * + * some regular AZERTY special chars based on a French AZERTY Layout are added for + * your convenience (so you can refer to them if you have a similar layout) : + * ²)=^$ù*! + */ + + public static const NUMBER_0:uint = 48; + public static const NUMBER_1:uint = 49; + public static const NUMBER_2:uint = 50; + public static const NUMBER_3:uint = 51; + public static const NUMBER_4:uint = 52; + public static const NUMBER_5:uint = 53; + public static const NUMBER_6:uint = 54; + public static const NUMBER_7:uint = 55; + public static const NUMBER_8:uint = 56; + public static const NUMBER_9:uint = 57; + + public static const A:uint = 65; + public static const B:uint = 66; + public static const C:uint = 67; + public static const D:uint = 68; + public static const E:uint = 69; + public static const F:uint = 70; + public static const G:uint = 71; + public static const H:uint = 72; + public static const I:uint = 73; + public static const J:uint = 74; + public static const K:uint = 75; + public static const L:uint = 76; + public static const M:uint = 77; + public static const N:uint = 78; + public static const O:uint = 79; + public static const P:uint = 80; + public static const Q:uint = 81; + public static const R:uint = 82; + public static const S:uint = 83; + public static const T:uint = 84; + public static const U:uint = 85; + public static const V:uint = 86; + public static const W:uint = 87; + public static const X:uint = 88; + public static const Y:uint = 89; + public static const Z:uint = 90; + + public static const BACKSPACE:uint = 8; + public static const TAB:uint = 9; + public static const ENTER:uint = 13; + public static const SHIFT:uint = 16; + public static const CTRL:uint = 17; + public static const CAPS_LOCK:uint = 20; + public static const ESCAPE:uint = 27; + public static const SPACE:uint = 32; + + public static const PAGE_UP:uint = 33; + public static const PAGE_DOWN:uint = 34; + public static const END:uint = 35; + public static const HOME:uint = 36; + + public static const LEFT:uint = 37; + public static const UP:uint = 38; + public static const RIGHT:uint = 39; + public static const DOWN:uint = 40; + + public static const INSERT:uint = 45; + public static const DELETE:uint = 46; + public static const BREAK:uint = 19; + public static const NUM_LOCK:uint = 144; + public static const SCROLL_LOCK:uint = 145; + + public static const NUMPAD_0:uint = 96; + public static const NUMPAD_1:uint = 97; + public static const NUMPAD_2:uint = 98; + public static const NUMPAD_3:uint = 99; + public static const NUMPAD_4:uint = 100; + public static const NUMPAD_5:uint = 101; + public static const NUMPAD_6:uint = 102; + public static const NUMPAD_7:uint = 103; + public static const NUMPAD_8:uint = 104; + public static const NUMPAD_9:uint = 105; + + public static const NUMPAD_MULTIPLY:uint = 105; + public static const NUMPAD_ADD:uint = 107; + public static const NUMPAD_ENTER:uint = 13; + public static const NUMPAD_SUBTRACT:uint = 109; + public static const NUMPAD_DECIMAL:uint = 110; + public static const NUMPAD_DIVIDE:uint = 111; + + public static const F1:uint = 112; + public static const F2:uint = 113; + public static const F3:uint = 114; + public static const F4:uint = 115; + public static const F5:uint = 116; + public static const F6:uint = 117; + public static const F7:uint = 118; + public static const F8:uint = 119; + public static const F9:uint = 120; + public static const F10:uint = 121; + public static const F11:uint = 122; + public static const F12:uint = 123; + public static const F13:uint = 124; + public static const F14:uint = 125; + public static const F15:uint = 126; + + public static const COMMAND:uint = 15; + public static const ALTERNATE:uint = 18; + + public static const BACKQUOTE:uint = 192; + public static const QUOTE:uint = 222; + public static const COMMA:uint = 188; + public static const PERIOD:uint = 190; + public static const SEMICOLON:uint = 186; + public static const BACKSLASH:uint = 220; + public static const SLASH:uint = 191; + + public static const EQUAL:uint = 187; + public static const MINUS:uint = 189; + + public static const LEFT_BRACKET:uint = 219; + public static const RIGHT_BRACKET:uint = 221; + + public static const AUDIO:uint = 0x01000017; + public static const BACK:uint = 0x01000016; + public static const MENU:uint = 0x01000012; + public static const SEARCH:uint = 0x0100001F; + + //HELPER FOR AZERTY ---------------------------------- + public static const AZERTY_SQUARE:uint = 222; // ² + public static const AZERTY_RIGHT_PARENTHESIS:uint = 219; + public static const AZERTY_CIRCUMFLEX:uint = 221; // ^ + public static const AZERTY_DOLLAR_SIGN:uint = 186; // $ + public static const AZERTY_U_GRAVE:uint = 192; // ù + public static const AZERTY_MULTIPLY:uint = 220; // * + public static const AZERTY_EXCLAMATION_MARK:uint = 223; // ! + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/TimeShifter.as b/src/citrus/input/controllers/TimeShifter.as new file mode 100644 index 00000000..76cf645c --- /dev/null +++ b/src/citrus/input/controllers/TimeShifter.as @@ -0,0 +1,513 @@ +package citrus.input.controllers { + + import citrus.input.InputAction; + import citrus.input.InputController; + + import org.osflash.signals.Signal; + + /** + * Work In Progress. + */ + public class TimeShifter extends InputController + { + public var onSpeedChanged:Signal; + public var onActivated:Signal; + public var onDeactivated:Signal; + public var onEndOfBuffer:Signal; + + protected var _paused:Boolean = false; + protected var _active:Boolean = false; + + protected var _Buffer:Vector.; + + /** + * a "bufferSet" helps knowing what to record and from what. + * the set needs the following properties : + * object : the object to record from + * continuous : the parameters of this object that will get interpolated such as position, velocity. + * discrete : the parameters of this object that will not get interpolated, such as scores, animation, Booleans... + * + * ex : {object: hero, continuous:["x","y","rotation"], discrete: ["animation","animationFrame"]} + * + * to record and replay animation sequences, you can add something like this to a default Physics Object : + * + * public function get animationFrame():uint { + * return (_view as AnimationSequence).mcSequences[_animation].currentFrame; + * } + * public function set animationFrame(value:uint):void { + * (_view as AnimationSequence).mcSequences[_animation].currentFrame = value; + * } + * + * as long as you are sure that _view will be an AnimationSequence. + * then puttin "animationFrame to the discrete param list in a bufferSet will record and replay the correct frame! + * + * note: "continuous" or "discrete" parameters can be arrays. + */ + protected var _BufferSets:Vector.; + + protected var _bufferPosition:Number = 0; + protected var _bufferLength:uint = 0; + protected var _maxBufferLength:uint; + + protected var _previousBufferIndex:uint; + protected var _nextBufferIndex:uint; + + protected var _previousBufferFrame:Object; + protected var _nextBufferFrame:Object; + + protected var _elapsedFrameCount:uint = 0; + protected var _isBufferFrame:Boolean = true; + + protected var _interpolationFactor:Number = 1; + + protected var _previousSpeed:Number = 0; // used for knowing direction. + protected var _currentSpeed:Number = 0; + protected var _targetSpeed:Number = 0; + + protected var _easeFunc:Function; + protected var _easeTimer:uint = 0; + protected var _easeDuration:uint = 80; + + public var isAtEOFLeft:Boolean = false; + public var isAtEOFRight:Boolean = false; + + /** + * saves a factor accessible on speed transitions. + */ + public var easeFactor:Number = 0; + + protected var _doDelay:Boolean = false; + protected var _playbackDelay:Number = 0; + protected var _delayedFunc:Function; + + protected var _routeActions:Boolean; + protected var _replayActions:Boolean; + + protected var _manualMode:Boolean = false; + + /** + * read the docs for _Buffer to understand how to setup recordings. + * @param bufferInSeconds how long in the past should the TimeShifter record + * @param replayActions should the actions be recorded then replayed + * @param routeActions should the actions be routed automatically when the time shifter is active + */ + public function TimeShifter(bufferInSeconds:uint, replayActions:Boolean = false,routeActions:Boolean = true) + { + super("TimeShifter Controller"); + _updateEnabled = true; + + defaultChannel = 16; + + _maxBufferLength = bufferInSeconds * _ce.stage.frameRate; + + _Buffer = new Vector.(); + _BufferSets = new Vector.(); + + _easeFunc = Tween_easeOut; + + _replayActions = replayActions; + _routeActions = routeActions; + + onSpeedChanged = new Signal(); + onActivated = new Signal(); + onDeactivated = new Signal(); + onEndOfBuffer = new Signal(); + onSpeedChanged.add(onSpeedChange); + } + + /** + * Adds a "bufferSet" to the record. + * @param bufferSet {object:Object, continuous:["x","y"], discrete:["boolean"] } + */ + public function addBufferSet(bufferSet:Object):void + { + if (_active) + throw(new Error("you can't add a bufferSet to TimeShifter if it's active.")); + else + if(bufferSet.object && (bufferSet.continuous || bufferSet.discrete)) + _BufferSets.push(bufferSet); + } + + /** + * starts replay with an optional delay. + * @param delay in seconds + */ + public function startReplay(delay:Number = 0, speed:Number = 1):void + { + _playbackDelay = (delay < 0) ? Math.abs(delay) * _ce.stage.frameRate : delay * _ce.stage.frameRate; + _doDelay = true; + _delayedFunc = replay; + (speed < 0) ? onSpeedChanged.dispatch(-speed) : onSpeedChanged.dispatch(speed) ; + } + + /** + * starts rewind with an optional delay. + * @param delay in seconds + */ + public function startRewind(delay:Number = 0, speed:Number = 1):void + { + _playbackDelay = (delay < 0) ? Math.abs(delay) * _ce.stage.frameRate : delay * _ce.stage.frameRate; + _doDelay = true; + _delayedFunc = rewind; + (speed < 0) ? onSpeedChanged.dispatch(speed) : onSpeedChanged.dispatch(-speed) ; + } + + protected function replay():void + { + _bufferPosition = _previousBufferIndex = 0; + _nextBufferIndex = 1; + _active = true; + onActivated.dispatch(); + if(_routeActions) + _input.startRouting(defaultChannel); + } + + protected function rewind():void + { + _bufferPosition = _previousBufferIndex = _bufferLength - 1; + _nextBufferIndex = _bufferLength - 2; + _active = true; + onActivated.dispatch(); + if(_routeActions) + _input.startRouting(defaultChannel); + } + + public function pause():void + { + _bufferPosition = _previousBufferIndex = _nextBufferIndex = _Buffer.length - 2; + onActivated.dispatch(); + _currentSpeed = 0; + onSpeedChanged.dispatch(0); + _active = true; + _paused = true; + } + + public function startManualControl(startSpeed:Number):void + { + _bufferPosition = _previousBufferIndex =_Buffer.length - 1; + _nextBufferIndex =_Buffer.length - 2; + _active = true; + onActivated.dispatch(); + if(_routeActions) + _input.startRouting(defaultChannel); + _currentSpeed = startSpeed; + onSpeedChanged.dispatch(startSpeed); + } + + protected function onSpeedChange(value:Number):void + { + _easeTimer = 0; + _targetSpeed = value; + } + + protected function checkActions():void + { + if (_input.justDid("timeshift", defaultChannel) && (!_active || _paused)) + { + _manualMode = true; + _paused = false; + startManualControl( -1); + } + + //speed change on playback and when input is routed on manual mode. + + if (_input.justDid("down", defaultChannel) && _active && _manualMode) + onSpeedChanged.dispatch(_targetSpeed - 1); + if (_input.justDid("up", defaultChannel) && _active && _manualMode) + onSpeedChanged.dispatch(_targetSpeed + 1); + + //Key up + + if (!_input.isDoing("timeshift", defaultChannel) && (_active || !_paused) && _manualMode) + { + _manualMode = false; + reset(); + } + + } + + protected function writeBuffer():void + { + var obj:Object; + var continuous:Object; + var discrete:Object; + if(_replayActions) + var abuff:Vector. = _input.getActionsSnapshot(); + var wbuff:Vector. = new Vector.(); + var ic:Object; + var id:Object; + var newbuffer:Object; + for each (obj in _BufferSets) + { + newbuffer = { }; + newbuffer.object = obj.object; + if (obj.continuous) + for each (continuous in obj.continuous) + if (obj.object[continuous] is Array) + { + newbuffer[continuous] = new Object(); + for each (ic in obj.object[continuous]) + newbuffer[continuous][ic] = obj.object[continuous][ic]; + } + else + newbuffer[continuous] = obj.object[continuous]; + + if (obj.discrete) + for each (discrete in obj.discrete) + if (obj.object[discrete] is Array) + { + newbuffer[discrete] = new Object(); + for each (id in obj.object[continuous]) + newbuffer[discrete][id] = obj.object[discrete][id]; + } + else + newbuffer[discrete] = obj.object[discrete]; + + wbuff.push(newbuffer); + + } + + var buff:Object = { }; + buff["watchbuffer"] = wbuff; + if(_replayActions) + buff["actionbuffer"] = abuff; + + _Buffer.push(buff); + _bufferLength++; + + if (_bufferLength > _maxBufferLength) + { + _Buffer.shift(); + _bufferLength--; + } + + } + + /** + * Moves buffer position + * sets previous and next buffer index and the interpolation factor. + */ + protected function moveBufferPosition():void + { + if (Math.ceil(_bufferPosition + _currentSpeed) < _bufferLength - 1 && Math.floor(_bufferPosition + _currentSpeed) > 0 ) + { + _previousBufferIndex = (_currentSpeed - _previousSpeed < 0) ? Math.floor(_bufferPosition + _currentSpeed) : Math.ceil(_bufferPosition + _currentSpeed); + _nextBufferIndex = (_currentSpeed - _previousSpeed < 0) ? Math.floor(_bufferPosition + _currentSpeed) - 1 : Math.ceil(_bufferPosition + _currentSpeed) + 1; + _interpolationFactor = (_currentSpeed - _previousSpeed < 0) ? _nextBufferIndex - (_bufferPosition + _currentSpeed) : (_bufferPosition + _currentSpeed) - _previousBufferIndex; + } + + _bufferPosition += _currentSpeed; + _isBufferFrame = !((_bufferPosition) % 1); + + _previousBufferFrame = _Buffer[_previousBufferIndex]; + _nextBufferFrame = _Buffer[_nextBufferIndex]; + + if (_bufferPosition > _bufferLength) + _bufferPosition = _bufferLength - 1; + else if (_bufferPosition < 0) + _bufferPosition = 0; + } + + /** + * Sets all objects properties according to position in buffer (and interpolates). + */ + protected function readBuffer():void + { + var obj:Object; + var obj2:Object; + var continuous:Object; + var discrete:Object; + var buffset:Object; + var ic:Object; + var id:Object; + + for each (obj in _previousBufferFrame.watchbuffer) + { + for each (obj2 in _nextBufferFrame.watchbuffer) + { + for each (buffset in _BufferSets) + { + if (buffset.object == obj.object && obj.object == obj2.object) + { + if (buffset.continuous) + for each (continuous in buffset.continuous) + if (obj.object[continuous] is Array) + for each (ic in continuous) + obj.object[continuous][ic] = obj[continuous][ic] + ((obj2[continuous][ic] - obj[continuous][ic]) * _interpolationFactor) ; + else + obj.object[continuous] = obj[continuous] + ((obj2[continuous] - obj[continuous]) * _interpolationFactor) ; + + if (buffset.discrete) + for each (discrete in buffset.discrete) + if (obj.object[discrete] is Array) + for each (id in discrete) + obj.object[discrete][id] = obj[discrete][id]; + else + obj.object[discrete] = obj[discrete]; + } + } + } + } + + if(_replayActions) + for each (obj in _previousBufferFrame.actionbuffer) + _input.addOrSetAction(obj as InputAction); + + } + + /** + * Process speed easing + */ + protected function processSpeed():void + { + if (_paused) + return; + if (_easeTimer < _easeDuration) + { + _easeTimer++; + _currentSpeed = _easeFunc(_easeTimer, _currentSpeed, _targetSpeed - _currentSpeed, _easeDuration); + easeFactor = 1 - Math.abs(_currentSpeed - _targetSpeed); + } + _previousSpeed = _currentSpeed; + + } + + /* + * Tweening functions for speed . equations by Robert Penner. + */ + private function Tween_easeOut(t:Number, b:Number, c:Number, d:Number):Number { t /= d; return -c * t*(t-2) + b; } + private function Tween_easeIn(t:Number, b:Number, c:Number, d:Number):Number { t /= d; return c * t * t + b;} + private function Tween_linear(t:Number, b:Number, c:Number, d:Number):Number { t /= d; return c*t/d + b; } + + override public function update():void + { + if (!enabled) + return; + + checkActions(); + + if (!_active) + { + if(!_paused) + writeBuffer(); + + if(_doDelay) + if (_playbackDelay > 0 ) + _playbackDelay--; + else + { + _doDelay = false; + _delayedFunc(); + } + } + else + { + processSpeed(); + moveBufferPosition(); + readBuffer(); + + if (_bufferPosition <= 0) + { + isAtEOFRight = false; + isAtEOFLeft = true; + } + else if (_bufferPosition >= _bufferLength - 1) + { + isAtEOFRight = true; + isAtEOFLeft = false; + } + else if (_bufferPosition > 0 && _bufferPosition < _bufferLength - 1) + { + isAtEOFRight = false; + isAtEOFLeft = false; + } + + if (isAtEOFLeft || isAtEOFRight) + { + onEndOfBuffer.dispatch(); + } + + if (!_manualMode) + if (_bufferLength > 0 && (isAtEOFLeft || isAtEOFRight)) + reset(); + + _elapsedFrameCount++; + } + } + + public function reset():void + { + processSpeed(); + moveBufferPosition(); + readBuffer(); + + _elapsedFrameCount = 0; + + _bufferPosition = 0; + + //cut only the future : + _Buffer.splice(_nextBufferIndex, _Buffer.length - 1); + _bufferLength = _Buffer.length; + + _previousBufferIndex = 0; + _nextBufferIndex = 0; + + _previousSpeed = 0; + _currentSpeed = 0; + _targetSpeed = 0; + + isAtEOFRight = false; + isAtEOFLeft = false; + + _active = false; + onDeactivated.dispatch(); + _doDelay = false; + + _input.resetActions(); + if(_routeActions) + _input.stopRouting(); + } + + override public function destroy():void + { + reset(); + + _BufferSets.length = 0; + _bufferLength = _Buffer.length = 0; + _previousBufferFrame = null; + _nextBufferFrame = null; + _delayedFunc = null; + + onSpeedChanged.removeAll(); + onActivated.removeAll(); + onDeactivated.removeAll(); + onEndOfBuffer.removeAll(); + + super.destroy(); + } + + /** + * returns the current speed of TimeShifter playback. + */ + public function get speed():Number + { + return _currentSpeed; + } + + public function get targetSpeed():Number + { + return _targetSpeed; + } + + public function get paused():Boolean + { + return _paused; + } + + public function get active():Boolean + { + return _active; + } + + } +} \ No newline at end of file diff --git a/src/citrus/input/controllers/displaylist/VirtualButton.as b/src/citrus/input/controllers/displaylist/VirtualButton.as new file mode 100644 index 00000000..6fae87b0 --- /dev/null +++ b/src/citrus/input/controllers/displaylist/VirtualButton.as @@ -0,0 +1,106 @@ +package citrus.input.controllers.displaylist { + + import citrus.input.controllers.AVirtualButton; + + import flash.display.Sprite; + import flash.events.MouseEvent; + + public class VirtualButton extends AVirtualButton + { + public var graphic:Sprite; //main Sprite container. + + protected var button:Sprite; + + public var buttonUpGraphic:Sprite; + public var buttonDownGraphic:Sprite; + + public function VirtualButton(name:String, params:Object = null) + { + graphic = new Sprite(); + super(name, params); + _x = _x ? _x : _ce.stage.stageWidth - (_margin + 3*_buttonradius) ; + _y = _y ? _y : _ce.stage.stageHeight - 3*_buttonradius; + + initGraphics(); + } + + override protected function initGraphics():void + { + button = new Sprite(); + + if (!buttonUpGraphic) + { + buttonUpGraphic = new Sprite(); + buttonUpGraphic.graphics.beginFill(0x000000, 0.1); + buttonUpGraphic.graphics.drawCircle(0, 0, _buttonradius); + buttonUpGraphic.graphics.endFill(); + } + + if (!buttonDownGraphic) + { + buttonDownGraphic = new Sprite(); + buttonDownGraphic.graphics.beginFill(0xEE0000, 0.85); + buttonDownGraphic.graphics.drawCircle(0, 0, _buttonradius); + buttonDownGraphic.graphics.endFill(); + } + + button.addChild(buttonUpGraphic); + + graphic.addChild(button); + + graphic.x = _x; + graphic.y = _y; + + //Add graphic + _ce.stage.addChild(graphic); + + //MOUSE EVENTS + + graphic.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseEvent); + graphic.addEventListener(MouseEvent.MOUSE_UP, handleMouseEvent); + } + + private function handleMouseEvent(e:MouseEvent):void + { + + if (e.type == MouseEvent.MOUSE_DOWN && button == e.target.parent) + { + triggerON(buttonAction, 1, null, buttonChannel); + button.removeChildAt(0); + button.addChild(buttonDownGraphic); + + _ce.stage.addEventListener(MouseEvent.MOUSE_UP, handleMouseEvent); + } + + if (e.type == MouseEvent.MOUSE_UP && button == e.target.parent) + { + triggerOFF(buttonAction, 0, null, buttonChannel); + button.removeChildAt(0); + button.addChild(buttonUpGraphic); + + _ce.stage.removeEventListener(MouseEvent.MOUSE_UP, handleMouseEvent); + } + } + + override public function get visible():Boolean + { + return _visible = graphic.visible; + } + + override public function set visible(value:Boolean):void + { + _visible = graphic.visible = value; + } + + override public function destroy():void + { + //remove mouse events. + graphic.removeEventListener(MouseEvent.MOUSE_DOWN, handleMouseEvent); + graphic.removeEventListener(MouseEvent.MOUSE_UP, handleMouseEvent); + + //remove graphic + _ce.stage.removeChild(graphic); + } + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/displaylist/VirtualJoystick.as b/src/citrus/input/controllers/displaylist/VirtualJoystick.as new file mode 100644 index 00000000..c1ac92ba --- /dev/null +++ b/src/citrus/input/controllers/displaylist/VirtualJoystick.as @@ -0,0 +1,193 @@ +package citrus.input.controllers.displaylist { + + import citrus.input.controllers.AVirtualJoystick; + + import flash.display.Sprite; + import flash.events.MouseEvent; + + /** + * Simple Flash Virtual Joystick + */ + public class VirtualJoystick extends AVirtualJoystick + { + public var graphic:Sprite; //main Sprite container. + + //separate joystick elements + public var back:Sprite; + public var knob:Sprite; + + public function VirtualJoystick(name:String, params:Object = null) + { + graphic = new Sprite(); + super(name, params); + + _innerradius = _radius - _knobradius; + + _x = _x ? _x : 2*_innerradius; + _y = _y ? _y : _ce.stage.stageHeight - 2*_innerradius; + + initActionRanges(); + initGraphics(); + + _updateEnabled = true; + } + + override protected function initGraphics():void + { + + if (!back) + { + //draw back + back = new Sprite(); + back.graphics.beginFill(0x000000, 0.1); + back.graphics.drawCircle(0, 0, _radius); + + //draw arrows + + var m:int = 15; // margin + var w:int = 30; // width + var h:int = 40; // height + + back.graphics.beginFill(0x000000, 0.2); + back.graphics.moveTo(0, -_radius + m); + back.graphics.lineTo(-w, -_radius + h); + back.graphics.lineTo(w, -_radius + h); + back.graphics.endFill(); + + back.graphics.beginFill(0x000000, 0.2); + back.graphics.moveTo(0, _radius - m); + back.graphics.lineTo(-w, _radius - h); + back.graphics.lineTo(+w, _radius - h); + back.graphics.endFill(); + + back.graphics.beginFill(0x000000, 0.2); + back.graphics.moveTo(-_radius + m, 0); + back.graphics.lineTo(-_radius + h, -w); + back.graphics.lineTo(-_radius + h, w); + back.graphics.endFill(); + + back.graphics.beginFill(0x000000, 0.2); + back.graphics.moveTo(_radius - m, 0); + back.graphics.lineTo(_radius - h, -w); + back.graphics.lineTo(_radius - h, +w); + back.graphics.endFill(); + + } + + if (!knob) + { + knob = new Sprite(); + + knob.graphics.beginFill(0xEE0000, 0.85); + knob.graphics.drawCircle(0, 0, _knobradius); + } + + graphic.addChild(back); + graphic.addChild(knob); + + graphic.x = _x; + graphic.y = _y; + + _ce.stage.addChild(graphic); + + graphic.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseEvent); + } + + private function handleMouseEvent(e:MouseEvent):void + { + if (e.type == MouseEvent.MOUSE_DOWN && !_grabbed) + { + _grabbed = true; + _centered = false; + handleGrab(graphic.mouseX, graphic.mouseY); + + _ce.stage.addEventListener(MouseEvent.MOUSE_UP, handleMouseEvent); + _ce.stage.addEventListener(MouseEvent.MOUSE_MOVE, handleMouseEvent); + + graphic.removeEventListener(MouseEvent.MOUSE_DOWN, handleMouseEvent); + } + + if (e.type == MouseEvent.MOUSE_MOVE && _grabbed) + handleGrab(graphic.mouseX, graphic.mouseY); + + if (e.type == MouseEvent.MOUSE_UP && _grabbed) + { + graphic.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseEvent); + + _ce.stage.removeEventListener(MouseEvent.MOUSE_UP, handleMouseEvent); + _ce.stage.removeEventListener(MouseEvent.MOUSE_MOVE, handleMouseEvent); + + handleGrab(graphic.mouseX, graphic.mouseY); + reset(); + _grabbed = false; + } + } + + //properties for knob tweening. + private var _vx:Number = 0; + private var _vy:Number = 0; + private var _spring:Number = 400; + private var _friction:Number = 0.0005; + + override public function update():void + { + if (visible) + { + //update knob graphic + if (_grabbed) + { + knob.x = _targetPosition.x; + knob.y = _targetPosition.y; + } + else if (!_centered && !((knob.x > -0.5 && knob.x < 0.5) && (knob.y > -0.5 && knob.y < 0.5))) + { + //http://snipplr.com/view/51769/ + _vx += -knob.x * _spring; + _vy += -knob.y * _spring; + + knob.x += (_vx *= _friction); + knob.y += (_vy *= _friction); + } + else + _centered = true; + + if (_grabbed) + graphic.alpha = activeAlpha; + else + graphic.alpha = inactiveAlpha; + + } + } + + override protected function reset():void + { + super.reset(); + graphic.x = _x; + graphic.y = _y; + } + + public function get visible():Boolean + { + return _visible = graphic.visible; + } + + public function set visible(value:Boolean):void + { + graphic.visible = _visible = value; + } + + override public function destroy():void + { + graphic.removeEventListener(MouseEvent.MOUSE_DOWN, handleMouseEvent); + + _ce.stage.removeChild(graphic); + + _xAxisActions = null; + _yAxisActions = null; + + super.destroy(); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/GamePadButtonRebinder.as b/src/citrus/input/controllers/gamepad/GamePadButtonRebinder.as new file mode 100644 index 00000000..89a1fb38 --- /dev/null +++ b/src/citrus/input/controllers/gamepad/GamePadButtonRebinder.as @@ -0,0 +1,148 @@ +package citrus.input.controllers.gamepad +{ + import citrus.input.controllers.gamepad.controls.ButtonController; + import citrus.input.InputAction; + import citrus.input.InputController; + import flash.events.TimerEvent; + import flash.utils.Timer; + import org.osflash.signals.Signal; + + /** + * Experimental InputController that waits for a new gamepad buttons pressed to assign a new button to it. + * + * var buttonRebinder:GamePadButtonRebinder = new GamePadButtonRebinder("", "down", true, true, 5000); + * buttonRebinder.onDone.add(function(ok:Boolean):void + * { + * if (ok) + * trace("ACTION HAS BEEN REBOUND CORRECTLY."); + * else + * trace("ACTION HAS NOT BEEN REBOUND, TIMER IS COMPLETE."); + * }); + */ + public class GamePadButtonRebinder extends InputController + { + + protected var _actionName:String; + protected var _route:Boolean; + protected var _removeActions:Boolean; + protected var _gamePadManager:GamePadManager; + protected var _gamePads:Vector.; + protected var _gamePadIndex:int; + protected var _gamePad:Gamepad; + protected var _timeOut:int; + protected var _timer:Timer; + + private var _success:Boolean = false; + + /** + * dispatches true if rebound correctly, or false if timer is over. + */ + public var onDone:Signal; + + public function GamePadButtonRebinder(name:String, action:String, route:Boolean = true, removeActions:Boolean = true , timeOut:int = -1, gamePadIndex:int = -1) + { + _actionName = action; + _route = route; + _removeActions = removeActions; + super(name); + _updateEnabled = true; + _gamePadIndex = gamePadIndex; + + _gamePadManager = GamePadManager.getInstance(); + _gamePads = new Vector.(); + var gp:Gamepad; + for (var i:int = 0; i < _gamePadManager.numGamePads; i++) + { + gp = _gamePadManager.getGamePadAt(i); + _gamePads.push(gp); + } + + onDone = new Signal(Boolean); + + _timeOut = timeOut; + + if (_gamePadIndex > -1) + { + _gamePad = _gamePadManager.getGamePadAt(_gamePadIndex); + _gamePad.triggerActivity = true; + } + else + for each (gp in _gamePads) + gp.triggerActivity = true; + + if (_route) + _input.startRouting(999); + + if (_timeOut > -1) + { + _timer = new Timer(_timeOut, 1); + _timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete); + _timer.start(); + } + } + + protected var time:int = 0; + override public function update():void + { + time++; + + if (time % 2 == 0) + return; + + var actions:Vector. = _input.getActions(999); + if (actions.length > 0) + { + for each (var action:InputAction in actions) + { + if (action.controller is ButtonController) + { + var b:ButtonController = action.controller as ButtonController; + if ((_gamePad && b.gamePad == _gamePad) || !_gamePad) + { + if(_removeActions) + b.gamePad.removeActionFromButtons(_actionName); + _input.stopActionsOf(b); // stop action of ButtonController + b.gamePad.setButtonAction(b.name, _actionName); //set new action + _success = true; + destroy(); //destroy self + break; + } + } + } + } + } + + protected function onTimerComplete(te:TimerEvent):void + { + te.target.removeEventListener(TimerEvent.TIMER_COMPLETE, onTimerComplete); + _updateEnabled = false; + destroy(); + } + + + override public function destroy():void + { + if (_gamePad) + { + _gamePad.triggerActivity = false; + _gamePad = null; + } + else + for each (var gp:Gamepad in _gamePads) + gp.triggerActivity = false; + + _gamePads.length = 0; + + if(_route) + _input.stopRouting(); + _input.resetActions(); + _gamePadManager = null; + super.destroy(); + + onDone.dispatch(_success); + onDone.removeAll(); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/GamePadManager.as b/src/citrus/input/controllers/gamepad/GamePadManager.as new file mode 100644 index 00000000..b33c4627 --- /dev/null +++ b/src/citrus/input/controllers/gamepad/GamePadManager.as @@ -0,0 +1,259 @@ +package citrus.input.controllers.gamepad +{ + import citrus.input.controllers.gamepad.maps.FreeboxGamepadMap; + import citrus.input.controllers.gamepad.maps.OUYAGamepadMap; + import citrus.input.controllers.gamepad.maps.PS3GamepadMap; + import citrus.input.controllers.gamepad.maps.Xbox360GamepadMap; + import citrus.input.InputController; + import flash.events.GameInputEvent; + import flash.ui.GameInput; + import flash.ui.GameInputDevice; + import flash.utils.Dictionary; + import org.osflash.signals.Signal; + + public class GamePadManager extends InputController + { + protected static var _gameInput:GameInput = new GameInput(); + + /** + * key = substring in devices id/name to recognize + * value = map class + */ + public var devicesMapDictionary:Dictionary; + + protected var _maxDevices:uint; + + protected var _gamePads:Dictionary; + //default map should extend GamePadMap, will be applied to each new device plugged in. + protected var _defaultMap:Class; + //maximum number of game input devices we can add (as gamepads) + protected var _maxPlayers:uint = 0; + //last channel used (by the last device plugged in) + protected var _lastChannel:uint = 0; + + protected static var _instance:GamePadManager; + + /** + * dispatches a newly created Gamepad object when a new GameInputDevice is added. + */ + public var onControllerAdded:Signal; + /** + * dispatches the Gamepad object corresponding to the GameInputDevice that got removed. + */ + public var onControllerRemoved:Signal; + + public function GamePadManager(maxPlayers:uint = 1, defaultMap:Class = null) + { + super("GamePadManager", null); + + _maxDevices = maxPlayers; + + if (!GameInput.isSupported) + { + trace(this, "GameInput is not supported."); + return; + } + + + initdevicesMapDictionaryMaps(); + _defaultMap = defaultMap; + + _gamePads = new Dictionary(); + + onControllerAdded = new Signal(Gamepad); + onControllerRemoved = new Signal(Gamepad); + + _gameInput.addEventListener(GameInputEvent.DEVICE_ADDED, handleDeviceAdded); + _gameInput.addEventListener(GameInputEvent.DEVICE_REMOVED, handleDeviceRemoved); + + + var numDevices:uint; + if ((numDevices = GameInput.numDevices) > 0) + { + var i:uint = 0; + var device:GameInputDevice; + for (; i < numDevices; i++) + { + device = GameInput.getDeviceAt(i); + if(device) + _gameInput.dispatchEvent(new GameInputEvent(GameInputEvent.DEVICE_ADDED, false, false, device)); + else + trace(this, "tried to get a device at", i, "and it returned null. please reference or initialize the GamePadManager sooner in your app!"); + } + } + + _instance = this; + } + + public static function getInstance():GamePadManager + { + return _instance; + } + + /** + * creates the dictionary for default game pad maps to apply. + * key = substring in GameInputDevice.name to look for, + * value = GamePadMap class to use for mapping the game pad correctly. + */ + protected function initdevicesMapDictionaryMaps():void + { + devicesMapDictionary = new Dictionary(); + devicesMapDictionary["Microsoft X-Box 360"] = Xbox360GamepadMap; + devicesMapDictionary["Xbox 360 Controller"] = Xbox360GamepadMap; + devicesMapDictionary["PLAYSTATION"] = PS3GamepadMap; + devicesMapDictionary["OUYA"] = OUYAGamepadMap; + devicesMapDictionary["Generic USB Joystick"] = FreeboxGamepadMap; + } + + /** + * apply map according to devicesMapDictionary dictionnary. + * @param gp + */ + protected function applyMap(device:GameInputDevice,gp:Gamepad):void + { + var substr:String; + for (substr in devicesMapDictionary) + if (device.name.indexOf(substr) > -1 || device.id.indexOf(substr) > -1) + { + gp.useMap(devicesMapDictionary[substr]); + return; + } + if (Gamepad.debug) + trace("[GamePadManager] No default map found in GamePadManager.devicesMapDictionary for", gp, ", trying to use defaultMap specified in the constructor."); + gp.useMap(_defaultMap); + } + + /** + * checks if device has a defined map in the devicesMapDictionary. + */ + public function isDeviceKnownGamePad(device:GameInputDevice):Boolean + { + var substr:String; + for (substr in devicesMapDictionary) + if (device.name.indexOf(substr) > -1 || device.id.indexOf(substr) > -1) + { + return true; + } + return false; + } + + /** + * return the first gamePad using the defined channel. + * @param channel + * @return + */ + public function getGamePadByChannel(channel:uint = 0):Gamepad + { + var pad:Gamepad; + for each(pad in _gamePads) + if (pad.defaultChannel == channel) + return pad; + return pad; + } + + public function getGamePadAt(index:int = 0):Gamepad + { + var c:int = 0; + for (var k:* in _gamePads) + { + if (c == index) + return _gamePads[k] as Gamepad; + c++; + } + return null; + } + + protected var numDevicesAdded:int = 0; + + protected function handleDeviceAdded(e:GameInputEvent):void + { + if (_gamePads.length >= _maxDevices) + return; + + var device:GameInputDevice = e.device; + var deviceID:String = device.id; + var pad:Gamepad; + + if (deviceID in _gamePads) + { + trace(deviceID, "already added"); + return; + } + + pad = new Gamepad("gamepad" + numDevicesAdded, device, null); + + //check if we know a map for this device and apply it. + applyMap(device,pad); + + numDevicesAdded++; + + if (numGamePads < _lastChannel) + { + pad.defaultChannel = _lastChannel - numGamePads; + } + else + { + pad.defaultChannel = _lastChannel++; + } + + _gamePads[pad.deviceID] = pad; + onControllerAdded.dispatch(pad); + } + + protected function handleDeviceRemoved(e:GameInputEvent):void + { + numDevicesAdded--; + var id:String; + var pad:Gamepad; + for (id in _gamePads) + { + pad = _gamePads[id]; + if (pad.device == e.device) + break; + } + + if (!pad) + return; + + delete _gamePads[id]; + pad.destroy(); + onControllerRemoved.dispatch(pad); + } + + override public function destroy():void + { + _gameInput.removeEventListener(GameInputEvent.DEVICE_ADDED, handleDeviceAdded); + _gameInput.removeEventListener(GameInputEvent.DEVICE_REMOVED, handleDeviceRemoved); + + var gp:Gamepad; + for (var name:String in _gamePads) + { + gp = _gamePads[name]; + delete _gamePads[name]; + gp.destroy(); + } + devicesMapDictionary = null; + _defaultMap = null; + onControllerAdded.removeAll(); + onControllerRemoved.removeAll(); + super.destroy(); + } + + public function get defaultMap():Class + { + return _defaultMap; + } + + public function get numGamePads():int + { + var count:int = 0; + for (var k:* in _gamePads) + count++; + return count; + } + + public static const GAMEPAD_ADDED_ACTION:String = "GAMEPAD_ADDED_ACTION"; + public static const GAMEPAD_REMOVED_ACTION:String = "GAMEPAD_REMOVED_ACTION"; + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/Gamepad.as b/src/citrus/input/controllers/gamepad/Gamepad.as new file mode 100644 index 00000000..212d8143 --- /dev/null +++ b/src/citrus/input/controllers/gamepad/Gamepad.as @@ -0,0 +1,488 @@ +package citrus.input.controllers.gamepad +{ + import citrus.input.controllers.gamepad.controls.ButtonController; + import citrus.input.controllers.gamepad.controls.Icontrol; + import citrus.input.controllers.gamepad.controls.StickController; + import citrus.input.controllers.gamepad.maps.GamePadMap; + import citrus.input.InputController; + import flash.events.Event; + import flash.ui.GameInputControl; + import flash.ui.GameInputDevice; + import flash.utils.describeType; + import flash.utils.Dictionary; + + public class Gamepad extends InputController + { + protected var _device:GameInputDevice; + protected var _deviceID:String; + + /** + * GameInputControls for the GameInputDevice, indexed by their id. + */ + protected var _controls:Dictionary; + + /** + * button controller used, indexed by name. + */ + protected var _buttons:Dictionary; + + /** + * stick controller used, indexed by name. + */ + protected var _sticks:Dictionary; + + /** + * controls being used, indexed by GameInputControl.id + * (quick access for onChange) + */ + protected var _usedControls:Dictionary; + + /** + * will trace information on the gamepad at runtime. + */ + public static var debug:Boolean = false; + + public static var activityChannel:int = 100; + + /** + * if set to true, all 'children controllers' will send an action with their controller name when active (value != 0) + * helps figuring out which button someone touches for remapping in game for example. + */ + public var _triggerActivity:Boolean = false; + + public function get triggerActivity():Boolean + { + return _triggerActivity; + } + + public function set triggerActivity(val:Boolean):void + { + if (_triggerActivity == val) + return; + + _triggerActivity = val; + + /*for each (var b:ButtonController in _buttons) + _input.stopActionsOf(b); + for each (var s:StickController in _sticks) + _input.stopActionsOf(s);*/ + } + + public function Gamepad(name:String, device:GameInputDevice, map:Class = null, params:Object = null) + { + super(name, params); + + _device = device; + _deviceID = _device.id; + _controls = new Dictionary(); + + enabled = true; + initControlsList(); + + _buttons = new Dictionary(); + _sticks = new Dictionary(); + + _usedControls = new Dictionary(); + } + + /** + * list all available controls by their control.id and start caching. + */ + protected function initControlsList():void + { + var controlNames:Vector. = new Vector.(); + var control:GameInputControl; + var i:int = 0; + var numcontrols:int = _device.numControls; + for (i; i < numcontrols; i++) + { + control = _device.getControlAt(i); + _controls[control.id] = control; + controlNames.push(control.id); + } + + _device.startCachingSamples(30, controlNames); + } + + /** + * This will parse all control names for substr, and if substr if present then will register is as a ButtonController + * if its not already registered. + * additionally its name will be prefixed with prefix (helps prevent confusion when triggerActivity is true for example). + * + * guessUnregisteredButtons is called by default when trying to apply a map that is either null, or not extending GamePadMap. + * @param substr + * @param prefix + */ + public function guessUnregisteredButtons(substr:String = "BUTTON_",prefix:String = "UNMAPPED_"):void + { + var name:String; + for each(var control:GameInputControl in _controls) + { + name = control.id; + if (name in _usedControls) + continue; + if (name.indexOf(substr) > -1) + registerButton(prefix + name, name); + } + } + + /** + * apply GamepadMap. + * calls guessUnregisteredButtons when the map is null or not extending GamePadMap. + * @param map + */ + public function useMap(map:Class):void + { + if (map != null) + { + var typeXML:XML = describeType(map); + if (typeXML.factory.extendsClass.(@type == "citrus.input.controllers.gamepad.maps::GamePadMap").length() > 0) + { + var mapconfig:GamePadMap = new map(); + mapconfig.setup(this); + + if(debug) + trace(name, "using map", map); + } + else if (debug) + { + trace(this, "unable to use the ", map, "map."); + trace(this, "will force default button registering"); + guessUnregisteredButtons(); + } + } + else + { + trace(this, "will force default button registering"); + guessUnregisteredButtons(); + } + + + stopAllActions(); + } + + protected function onChange(e:Event):void + { + if (!_enabled) + return; + + var id:String = (e.currentTarget as GameInputControl).id; + + if (!(id in _usedControls)) + { + if(debug) + trace(e.target.id, "seems to not be bound to any controls for", this); + return; + } + + var value:Number = (e.currentTarget as GameInputControl).value; + + var icontrols:Vector. = _usedControls[id]; + var icontrol:Icontrol; + + for each (icontrol in icontrols) + icontrol.updateControl(id, value); + + } + + protected function bindControl(controlid:String, controller:Icontrol):void + { + if (!(controlid in _controls)) + { + if(debug) + trace(this, "trying to bind", controlid, "but", controlid, "is not in listed controls for device", _device.name); + return; + } + + var control:GameInputControl = (_controls[controlid] as GameInputControl); + + if (!control.hasEventListener(Event.CHANGE)) + control.addEventListener(Event.CHANGE, onChange); + + if (!(controlid in _usedControls)) + _usedControls[controlid] = new Vector.(); + + if(debug) + trace("Binding", control.id, "to", controller); + + (_usedControls[controlid] as Vector.).push(controller); + } + + protected function unbindControl(controlid:String, controller:Icontrol):void + { + if (controlid in _usedControls) + { + if (_usedControls[controlid] is Vector.) + { + var controls:Vector. = _usedControls[controlid]; + var icontrol:Icontrol; + var i:String; + + for (i in controls) + { + icontrol = controls[i]; + if (icontrol == controller) + { + controls.splice(parseInt(i), 1); + break; + } + } + + if (controls.length == 0) + { + delete _usedControls[controlid]; + + if (_controls[controlid].hasEventListener(Event.CHANGE)) + _controls[controlid].removeEventListener(Event.CHANGE,onChange); + } + } + } + } + + public function unregisterStick(name:String):void + { + var stick:StickController; + stick = _sticks[name]; + if (stick) + { + unbindControl(stick.hAxis, stick); + unbindControl(stick.vAxis, stick); + delete _sticks[name]; + stick.destroy(); + } + } + + public function unregisterButton(name:String):void + { + var button:ButtonController; + button = _buttons[name]; + if (button) + { + unbindControl(button.controlID, button); + delete _buttons[name]; + button.destroy(); + } + } + + /** + * Register a new stick controller to the gamepad. + * leave all or any of up/right/down/left actions to null for these directions to trigger nothing. + * invertX and invertY inverts the axis values. + * @param name + * @param hAxis the GameInputControl id for the horizontal axis (left to right). + * @param vAxis the GameInputControl id for the vertical axis (up to donw). + * @param up + * @param right + * @param down + * @param left + * @param invertX + * @param invertY + * @return + */ + public function registerStick(name:String, hAxis:String, vAxis:String, up:String = null, right:String = null, down:String = null, left:String = null, invertX:Boolean = false, invertY:Boolean = false):StickController + { + if (name in _sticks) + { + if(debug) + trace(this + " joystick control " + name + " already exists"); + return _sticks[name]; + } + + var joy:StickController = new StickController(name,this, hAxis, vAxis, up, right, down, left, invertX, invertY); + bindControl(hAxis, joy); + bindControl(vAxis, joy); + return _sticks[name] = joy; + } + + /** + * Register a new button controller to the gamepad. + * if action is null, this button will trigger no action. + * @param name + * @param control_id the GameInputControl id. + * @param action + * @return + */ + public function registerButton(name:String, control_id:String, action:String = null):ButtonController + { + if (name in _buttons) + { + if(debug) + trace(this + " button control " + name + " already exists"); + return _buttons[name]; + } + var button:ButtonController = new ButtonController(name,this, control_id, action); + bindControl(control_id, button); + return _buttons[name] = button; + } + + + /** + * Set a registered stick's actions, leave null to keep unchanged. + * @param name + * @param up + * @param right + * @param down + * @param left + */ + public function setStickActions(name:String, up:String, right:String, down:String, left:String):void + { + if (!(name in _sticks)) + { + throw new Error(this + "cannot set joystick control, "+name+" is not registered."); + return; + } + + var joy:StickController = _sticks[name] as StickController; + + if (up) + joy.upAction = up; + if (right) + joy.rightAction = right; + if (down) + joy.downAction = down; + if (left) + joy.leftAction = left; + } + + /** + * Set a registered button controller action. + * @param name + * @param action + */ + public function setButtonAction(name:String, action:String):void + { + if (!(name in _buttons)) + { + throw new Error(this + " cannot set button control, " + name + " is not registered."); + } + + (_buttons[name] as ButtonController).action = action; + } + + public function swapButtonActions(button1Name:String, button2Name:String):void + { + var b1:ButtonController = getButton(button1Name); + var b2:ButtonController = getButton(button2Name); + if (!b1 || !b2) + return; + var action1:String = b1.action; + b1.action = b2.action; + b2.action = action1; + } + + public function removeActionFromControllers(actionName:String):void + { + removeActionFromButtons(actionName); + removeActionFromSticks(actionName); + } + + public function removeActionFromButtons(actionName:String):void + { + for each (var button:ButtonController in _buttons) + if (button.action == actionName ) + button.action = null; + } + + public function removeActionFromSticks(actionName:String):void + { + for each (var stick:StickController in _sticks) + { + if (stick.upAction == actionName) + { + stick.upAction = null; + continue; + } + + if (stick.rightAction == actionName) + { + stick.rightAction = null; + continue; + } + + if (stick.downAction == actionName) + { + stick.downAction = null; + continue; + } + + if (stick.leftAction == actionName) + { + stick.leftAction = null; + continue; + } + } + } + + /** + * get registered stick as a StickController to get access to the angle of the joystick for example. + * @param name + * @return + */ + public function getStick(name:String):StickController + { + if (name in _sticks) + return _sticks[name] as StickController; + return null; + } + + /** + * get added button as a ButtonController + * @param name + * @return + */ + public function getButton(name:String):ButtonController + { + if (name in _buttons) + return _buttons[name] as ButtonController; + return null; + } + + public function get device():GameInputDevice + { + return _device; + } + + public function get deviceID():String + { + return _deviceID; + } + + public function stopAllActions():void + { + var icontrols:Vector.; + var icontrol:Icontrol; + + for each (icontrols in _usedControls) + for each (icontrol in icontrols) + _ce.input.stopActionsOf(icontrol as InputController); + } + + override public function set enabled(val:Boolean):void + { + _device.enabled = _enabled = val; + } + + override public function destroy():void + { + var control:Icontrol; + for each (control in _buttons) + unregisterButton((control as InputController).name); + for each (control in _sticks) + unregisterButton((control as InputController).name); + + _usedControls = null; + _controls = null; + + enabled = false; + + _input.stopActionsOf(this); + + _buttons = null; + _sticks = null; + + super.destroy(); + + } + + } +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/controls/ButtonController.as b/src/citrus/input/controllers/gamepad/controls/ButtonController.as new file mode 100644 index 00000000..eb74e227 --- /dev/null +++ b/src/citrus/input/controllers/gamepad/controls/ButtonController.as @@ -0,0 +1,113 @@ +package citrus.input.controllers.gamepad.controls +{ + import citrus.input.controllers.gamepad.Gamepad; + import citrus.input.InputController; + + + public class ButtonController extends InputController implements Icontrol + { + protected var _gamePad:Gamepad; + protected var _controlID:String; + protected var _prevValue:Number = 0; + protected var _value:Number = 0; + protected var _action:String; + + protected var _active:Boolean = false; + + public var threshold:Number = 0.1; + public var inverted:Boolean = false; + public var precision:Number = 100; + public var digital:Boolean = false; + + /** + * ButtonController is an abstraction of the button controls of a gamepad. This InputController will see its value updated + * via its corresponding gamepad object and send his own actions to the Input system. + * + * It should not be instantiated manually. + */ + public function ButtonController(name:String,parentGamePad:Gamepad,controlID:String,action:String = null) + { + super(name); + _gamePad = parentGamePad; + _controlID = controlID; + _action = action; + } + + public function updateControl(control:String, value:Number):void + { + if (_action || _gamePad.triggerActivity) + { + value = value * (inverted ? -1 : 1); + _prevValue = _value; + value = ((value * precision) >> 0) / precision; + _value = ( value <= threshold && value >= -threshold ) ? 0 : value ; + _value = digital ? _value >> 0 : _value; + } + + if (_action) + { + if (_prevValue != _value) + { + if (_value > 0) + triggerCHANGE(_action, _value,null,_gamePad.defaultChannel); + else + triggerOFF(_action, 0, null, _gamePad.defaultChannel); + } + } + + if(_gamePad.triggerActivity) + active = _value > 0; + } + + protected function set active(val:Boolean):void + { + if (val == _active) + return; + + if (val) + triggerCHANGE(name, _value, null, Gamepad.activityChannel); + else + triggerOFF(name, 0, null, Gamepad.activityChannel); + + _active = val; + } + + public function hasControl(id:String):Boolean + { + return _controlID == id; + } + + override public function destroy():void + { + _gamePad = null; + super.destroy(); + } + + public function get value():Number + { + return _value; + } + + public function get gamePad():Gamepad + { + return _gamePad; + } + + public function get controlID():String + { + return _controlID; + } + + public function get action():String + { + return _action; + } + + public function set action(value:String):void + { + _action = value; + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/controls/Icontrol.as b/src/citrus/input/controllers/gamepad/controls/Icontrol.as new file mode 100644 index 00000000..68d41402 --- /dev/null +++ b/src/citrus/input/controllers/gamepad/controls/Icontrol.as @@ -0,0 +1,16 @@ +package citrus.input.controllers.gamepad.controls +{ + import citrus.input.controllers.gamepad.Gamepad; + + /** + * defines control wrappers we use in Gamepad. + */ + public interface Icontrol + { + function updateControl(control:String,value:Number):void + function hasControl(id:String):Boolean + function get gamePad():Gamepad + function destroy():void + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/controls/StickController.as b/src/citrus/input/controllers/gamepad/controls/StickController.as new file mode 100644 index 00000000..d0c87ce5 --- /dev/null +++ b/src/citrus/input/controllers/gamepad/controls/StickController.as @@ -0,0 +1,234 @@ +package citrus.input.controllers.gamepad.controls +{ + import citrus.input.controllers.gamepad.Gamepad; + import citrus.input.InputController; + import citrus.math.MathVector; + + public class StickController extends InputController implements Icontrol + { + protected var _gamePad:Gamepad; + + protected var _hAxis:String; + protected var _vAxis:String; + + protected var _prevRight:Number = 0; + protected var _prevLeft:Number = 0; + protected var _prevUp:Number = 0; + protected var _prevDown:Number = 0; + + protected var _vec:MathVector; + + public var upAction:String; + public var downAction:String; + public var leftAction:String; + public var rightAction:String; + + protected var _downActive:Boolean = false; + protected var _upActive:Boolean = false; + protected var _leftActive:Boolean = false; + protected var _rightActive:Boolean = false; + protected var _stickActive:Boolean = false; + + public var invertX:Boolean; + public var invertY:Boolean; + public var threshold:Number = 0.1; + public var precision:int = 100; + public var digital:Boolean = false; + + /** + * StickController is an abstraction of the stick controls of a gamepad. This InputController will see its axis values updated + * via its corresponding gamepad object and send his own actions to the Input system. + * + * It should not be instantiated manually. + * + * @param name + * @param hAxis left to right + * @param vAxis up to down + * @param up action name + * @param right action name + * @param down action name + * @param left action name + * @param invertX + * @param invertY + */ + public function StickController(name:String, parentGamePad:Gamepad,hAxis:String,vAxis:String, up:String = null, right:String = null, down:String = null, left:String = null, invertX:Boolean = false, invertY:Boolean = false) + { + super(name); + _gamePad = parentGamePad; + upAction = up; + downAction = down; + leftAction = left; + rightAction = right; + _hAxis = hAxis; + _vAxis = vAxis; + this.invertX = invertX; + this.invertY = invertY; + _vec = new MathVector(); + } + + public function hasControl(id:String):Boolean + { + return (id == _hAxis || id == _vAxis); + } + + public function updateControl(control:String, value:Number):void + { + value = ((value * precision) >> 0) / precision; + + value = (value <= threshold && value >= -threshold) ? 0 : value; + + if (control == _vAxis) + { + _prevUp = up; + _prevDown = down; + + _vec.y = (digital ? value >> 0 : value) * (invertY ? -1 : 1); + + if (downAction && _prevDown != down) + { + if (_downActive && (_prevDown > down || down == 0)) + { + triggerOFF(downAction, 0, null, _gamePad.defaultChannel); + _downActive = false; + } + if (down > 0) + { + triggerCHANGE(downAction, down, null, _gamePad.defaultChannel); + _downActive = true; + } + } + + if (upAction && _prevUp != up) + { + if (_upActive && (_prevUp > up || up == 0)) + { + triggerOFF(upAction, 0, null, _gamePad.defaultChannel); + _upActive = false; + } + if (up > 0) + { + triggerCHANGE(upAction, up, null, _gamePad.defaultChannel); + _upActive = true; + } + } + } + else if (control == _hAxis) + { + _prevLeft = left; + _prevRight = right; + + _vec.x = (digital ? value >> 0 : value) * (invertX ? -1 : 1); + + if (leftAction && _prevLeft != left) + { + if (_leftActive && _prevLeft > left || left == 0) + { + triggerOFF(leftAction, 0, null, _gamePad.defaultChannel); + _leftActive = false; + } + if (left > 0) + { + triggerCHANGE(leftAction, left, null, _gamePad.defaultChannel); + _leftActive = true; + } + } + + if (rightAction && _prevRight != right) + { + if (_rightActive && _prevRight > right || right == 0) + { + triggerOFF(rightAction, 0, null, _gamePad.defaultChannel); + _rightActive = false; + } + if (right > 0) + { + triggerCHANGE(rightAction, right, null, _gamePad.defaultChannel); + _rightActive = true; + } + } + } + + if(_gamePad.triggerActivity) + stickActive = _vec.length == 0 ? false : true; + + } + + protected function set stickActive(val:Boolean):void + { + if (val == _stickActive) + return; + else + { + if (val) + triggerCHANGE(name, 1, null, Gamepad.activityChannel); + else + triggerOFF(name, 0, null, Gamepad.activityChannel); + + _stickActive = val; + } + } + + public function get y():Number + { + return _vec.y; + } + + public function get x():Number + { + return _vec.x; + } + + public function get up():Number + { + return -_vec.y; + } + + public function get down():Number + { + return _vec.y; + } + + public function get left():Number + { + return -_vec.x; + } + + public function get right():Number + { + return _vec.x; + } + + public function get length():Number + { + return _vec.length; + } + + public function get angle():Number + { + return _vec.angle; + } + + public function get hAxis():String + { + return _hAxis; + } + + public function get vAxis():String + { + return _vAxis; + } + + public function get gamePad():Gamepad + { + return _gamePad; + } + + override public function destroy():void + { + _vec = null; + super.destroy(); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/maps/FreeboxGamepadMap.as b/src/citrus/input/controllers/gamepad/maps/FreeboxGamepadMap.as new file mode 100644 index 00000000..795c61db --- /dev/null +++ b/src/citrus/input/controllers/gamepad/maps/FreeboxGamepadMap.as @@ -0,0 +1,46 @@ +package citrus.input.controllers.gamepad.maps +{ + import citrus.input.controllers.gamepad.Gamepad; + /** + * This is the Freebox _gamepad controller preset + * It will work only in analog mode though (axes are weird when its not) + * http://www.lowcostmobile.com/img/operateurs/free/_gamepad_free.jpg + */ + public class FreeboxGamepadMap extends GamePadMap + { + public function FreeboxGamepadMap():void + { + + } + + override public function setupWIN():void + { + _gamepad.registerStick(GamePadMap.STICK_LEFT,"AXIS_1", "AXIS_0"); + _gamepad.registerStick(GamePadMap.STICK_RIGHT,"AXIS_4", "AXIS_2"); + + _gamepad.registerButton(GamePadMap.L1,"BUTTON_13"); + _gamepad.registerButton(GamePadMap.R1, "BUTTON_14"); + + _gamepad.registerButton(GamePadMap.L2, "BUTTON_15"); + _gamepad.registerButton(GamePadMap.R2, "BUTTON_16"); + + _gamepad.registerButton(GamePadMap.L3, "BUTTON_19"); + _gamepad.registerButton(GamePadMap.R3, "BUTTON_20"); + + _gamepad.registerButton(GamePadMap.SELECT, "BUTTON_17"); + _gamepad.registerButton(GamePadMap.START, "BUTTON_18"); + + _gamepad.registerButton(GamePadMap.DPAD_UP,"BUTTON_5","up"); + _gamepad.registerButton(GamePadMap.DPAD_DOWN,"BUTTON_6","down"); + _gamepad.registerButton(GamePadMap.DPAD_RIGHT,"BUTTON_8","right"); + _gamepad.registerButton(GamePadMap.DPAD_LEFT,"BUTTON_7","left"); + + _gamepad.registerButton(GamePadMap.BUTTON_TOP, "BUTTON_9"); + _gamepad.registerButton(GamePadMap.BUTTON_RIGHT, "BUTTON_10"); + _gamepad.registerButton(GamePadMap.BUTTON_BOTTOM, "BUTTON_11"); + _gamepad.registerButton(GamePadMap.BUTTON_LEFT, "BUTTON_12"); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/maps/GamePadMap.as b/src/citrus/input/controllers/gamepad/maps/GamePadMap.as new file mode 100644 index 00000000..3c0fef71 --- /dev/null +++ b/src/citrus/input/controllers/gamepad/maps/GamePadMap.as @@ -0,0 +1,90 @@ +package citrus.input.controllers.gamepad.maps +{ + import citrus.input.controllers.gamepad.Gamepad; + import flash.system.Capabilities; + + public class GamePadMap + { + protected static var _platform:String; + protected var _gamepad:Gamepad; + + public function GamePadMap():void + { + if(!_platform) + _platform = Capabilities.version.slice(0, 3); + } + + public function setup(gamepad:Gamepad):void + { + _gamepad = gamepad; + _gamepad.stopAllActions(); + + switch(_platform) + { + case "WIN" : + setupWIN(); + break; + case "MAC" : + setupMAC(); + break; + case "LNX" : + setupLNX(); + break; + case "AND" : + setupAND(); + break; + } + } + + /** + * force GamePadMap to use a certain platform when running : WIN,MAC,LNX,AND + */ + public static function set devPlatform(value:String):void { _platform = value; } + + /** + * override those functions to set up a gamepad for different OS's by default, + * or override setup() to define your own way. + */ + public function setupWIN():void {} + public function setupMAC():void {} + public function setupLNX():void {} + public function setupAND():void {} + + public static const L1:String = "L1"; + public static const R1:String = "R1"; + + public static const L2:String = "L2"; + public static const R2:String = "R2"; + + public static const STICK_LEFT:String = "STICK_LEFT"; + public static const STICK_RIGHT:String = "STICK_RIGHT"; + /** + * Joystick buttons. + */ + public static const L3:String = "L3"; + public static const R3:String = "R3"; + + public static const SELECT:String = "SELECT"; + public static const START:String = "START"; + + public static const HOME:String = "HOME"; + + /** + * directional button on the left of the game pad. + */ + public static const DPAD_UP:String = "DPAD_UP"; + public static const DPAD_RIGHT:String = "DPAD_RIGHT"; + public static const DPAD_DOWN:String = "DPAD_DOWN"; + public static const DPAD_LEFT:String = "DPAD_LEFT"; + + /** + * buttons on the right, conventionally 4 arranged as a rhombus , + * example, playstation controllers , with in the same order as below : triangle, square, cross, circle + */ + public static const BUTTON_TOP:String = "BUTTON_TOP"; + public static const BUTTON_RIGHT:String = "BUTTON_RIGHT"; + public static const BUTTON_BOTTOM:String = "BUTTON_BOTTOM"; + public static const BUTTON_LEFT:String = "BUTTON_LEFT"; + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/maps/OUYAGamepadMap.as b/src/citrus/input/controllers/gamepad/maps/OUYAGamepadMap.as new file mode 100644 index 00000000..198d36ab --- /dev/null +++ b/src/citrus/input/controllers/gamepad/maps/OUYAGamepadMap.as @@ -0,0 +1,60 @@ +package citrus.input.controllers.gamepad.maps +{ + import citrus.input.controllers.gamepad.controls.StickController; + import citrus.input.controllers.gamepad.Gamepad; + public class OUYAGamepadMap extends GamePadMap + { + + public function OUYAGamepadMap() + { + + } + + override public function setupAND():void + { + var joy:StickController; + + joy = _gamepad.registerStick(GamePadMap.STICK_LEFT,"AXIS_0", "AXIS_1"); + joy.threshold = 0.2; + + joy = _gamepad.registerStick(GamePadMap.STICK_RIGHT,"AXIS_11", "AXIS_14"); + joy.threshold = 0.2; + + _gamepad.registerButton(GamePadMap.L1,"BUTTON_102"); + _gamepad.registerButton(GamePadMap.R1, "BUTTON_103"); + + _gamepad.registerButton(GamePadMap.L2, "AXIS_17"); + _gamepad.registerButton(GamePadMap.R2, "AXIS_18"); + + _gamepad.registerButton(GamePadMap.L3, "BUTTON_106"); + _gamepad.registerButton(GamePadMap.R3, "BUTTON_107"); + + _gamepad.registerButton(GamePadMap.DPAD_UP,"BUTTON_19","up"); + _gamepad.registerButton(GamePadMap.DPAD_DOWN,"BUTTON_20","down"); + _gamepad.registerButton(GamePadMap.DPAD_RIGHT,"BUTTON_22","right"); + _gamepad.registerButton(GamePadMap.DPAD_LEFT,"BUTTON_21","left"); + + _gamepad.registerButton(GamePadMap.BUTTON_BOTTOM, "BUTTON_96"); // O + _gamepad.registerButton(GamePadMap.BUTTON_LEFT, "BUTTON_99"); // U + _gamepad.registerButton(GamePadMap.BUTTON_TOP, "BUTTON_100"); // Y + _gamepad.registerButton(GamePadMap.BUTTON_RIGHT, "BUTTON_97"); // A + } + + override public function setupLNX():void + { + setupAND(); + } + + override public function setupWIN():void + { + setupAND(); + } + + override public function setupMAC():void + { + setupAND(); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/maps/PS3GamepadMap.as b/src/citrus/input/controllers/gamepad/maps/PS3GamepadMap.as new file mode 100644 index 00000000..78c9064f --- /dev/null +++ b/src/citrus/input/controllers/gamepad/maps/PS3GamepadMap.as @@ -0,0 +1,93 @@ +package citrus.input.controllers.gamepad.maps +{ + import citrus.input.controllers.gamepad.controls.StickController; + import citrus.input.controllers.gamepad.Gamepad; + public class PS3GamepadMap extends GamePadMap + { + + public function PS3GamepadMap() + { + + } + + override public function setupMAC():void + { + var joy:StickController; + + joy = _gamepad.registerStick(GamePadMap.STICK_LEFT, "AXIS_0", "AXIS_1"); + joy.invertY = true; + joy.threshold = 0.2; + + joy = _gamepad.registerStick(GamePadMap.STICK_RIGHT, "AXIS_2", "AXIS_3"); + joy.invertY = true; + joy.threshold = 0.2; + + _gamepad.registerButton(GamePadMap.L1,"BUTTON_14"); + _gamepad.registerButton(GamePadMap.R1, "BUTTON_15"); + + _gamepad.registerButton(GamePadMap.L2, "BUTTON_12"); + _gamepad.registerButton(GamePadMap.R2, "BUTTON_13"); + + + _gamepad.registerButton(GamePadMap.SELECT, "BUTTON_4"); + _gamepad.registerButton(GamePadMap.START, "BUTTON_7"); + + _gamepad.registerButton(GamePadMap.L3, "BUTTON_5"); + _gamepad.registerButton(GamePadMap.R3, "BUTTON_6"); + + _gamepad.registerButton(GamePadMap.DPAD_UP,"BUTTON_8","up"); + _gamepad.registerButton(GamePadMap.DPAD_DOWN,"BUTTON_10","down"); + _gamepad.registerButton(GamePadMap.DPAD_RIGHT,"BUTTON_9","right"); + _gamepad.registerButton(GamePadMap.DPAD_LEFT,"BUTTON_11","left"); + + _gamepad.registerButton(GamePadMap.BUTTON_BOTTOM, "BUTTON_18"); // X + _gamepad.registerButton(GamePadMap.BUTTON_LEFT, "BUTTON_19"); // square + _gamepad.registerButton(GamePadMap.BUTTON_TOP, "BUTTON_16"); // triangle + _gamepad.registerButton(GamePadMap.BUTTON_RIGHT, "BUTTON_17"); // circle + } + + override public function setupAND():void + { + var joy:StickController; + + joy = _gamepad.registerStick(GamePadMap.STICK_LEFT, "AXIS_0", "AXIS_1"); + joy.threshold = 0.2; + + joy = _gamepad.registerStick(GamePadMap.STICK_RIGHT, "AXIS_11", "AXIS_14"); + joy.threshold = 0.2; + + _gamepad.registerButton(GamePadMap.L1,"BUTTON_102"); + _gamepad.registerButton(GamePadMap.R1, "BUTTON_103"); + + _gamepad.registerButton(GamePadMap.L2, "BUTTON_104"); + _gamepad.registerButton(GamePadMap.R2, "BUTTON_105"); + + _gamepad.registerButton(GamePadMap.START, "BUTTON_108"); + + _gamepad.registerButton(GamePadMap.L3, "BUTTON_106"); + _gamepad.registerButton(GamePadMap.R3, "BUTTON_107"); + + _gamepad.registerButton(GamePadMap.DPAD_UP,"AXIS_36","up"); + _gamepad.registerButton(GamePadMap.DPAD_DOWN,"AXIS_38","down"); + _gamepad.registerButton(GamePadMap.DPAD_RIGHT,"AXIS_37","right"); + _gamepad.registerButton(GamePadMap.DPAD_LEFT,"AXIS_39","left"); + + _gamepad.registerButton(GamePadMap.BUTTON_BOTTOM, "BUTTON_96"); // X + _gamepad.registerButton(GamePadMap.BUTTON_LEFT, "BUTTON_99"); // square + _gamepad.registerButton(GamePadMap.BUTTON_TOP, "BUTTON_100"); // triangle + _gamepad.registerButton(GamePadMap.BUTTON_RIGHT, "BUTTON_97"); // circle + } + + override public function setupWIN():void + { + setupAND(); + } + + override public function setupLNX():void + { + setupAND(); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/gamepad/maps/Xbox360GamepadMap.as b/src/citrus/input/controllers/gamepad/maps/Xbox360GamepadMap.as new file mode 100644 index 00000000..f7012f24 --- /dev/null +++ b/src/citrus/input/controllers/gamepad/maps/Xbox360GamepadMap.as @@ -0,0 +1,98 @@ +package citrus.input.controllers.gamepad.maps +{ + import citrus.input.controllers.gamepad.controls.ButtonController; + import citrus.input.controllers.gamepad.controls.StickController; + import citrus.input.controllers.gamepad.Gamepad; + + public class Xbox360GamepadMap extends GamePadMap + { + public function Xbox360GamepadMap():void + { + + } + + override public function setupMAC():void + { + setupWIN(); + } + + override public function setupLNX():void + { + setupWIN(); + } + + override public function setupWIN():void + { + var stick:StickController; + + stick = _gamepad.registerStick(GamePadMap.STICK_LEFT,"AXIS_0", "AXIS_1"); + stick.invertY = true; // AXIS_1 is inverted + stick.threshold = 0.2; + + stick = _gamepad.registerStick(GamePadMap.STICK_RIGHT,"AXIS_2", "AXIS_3"); + stick.invertY = true; // AXIS_3 is inverted + stick.threshold = 0.2; + + _gamepad.registerButton(GamePadMap.L1,"BUTTON_8"); + _gamepad.registerButton(GamePadMap.R1, "BUTTON_9"); + + _gamepad.registerButton(GamePadMap.L2, "BUTTON_10"); + _gamepad.registerButton(GamePadMap.R2, "BUTTON_11"); + + _gamepad.registerButton(GamePadMap.L3, "BUTTON_14"); + _gamepad.registerButton(GamePadMap.R3, "BUTTON_15"); + + _gamepad.registerButton(GamePadMap.SELECT, "BUTTON_12"); + _gamepad.registerButton(GamePadMap.START, "BUTTON_13"); + + _gamepad.registerButton(GamePadMap.DPAD_UP,"BUTTON_16","up"); + _gamepad.registerButton(GamePadMap.DPAD_DOWN,"BUTTON_17","down"); + _gamepad.registerButton(GamePadMap.DPAD_RIGHT,"BUTTON_19","right"); + _gamepad.registerButton(GamePadMap.DPAD_LEFT,"BUTTON_18","left"); + + _gamepad.registerButton(GamePadMap.BUTTON_TOP, "BUTTON_7"); + _gamepad.registerButton(GamePadMap.BUTTON_RIGHT, "BUTTON_5"); + _gamepad.registerButton(GamePadMap.BUTTON_BOTTOM, "BUTTON_4"); + _gamepad.registerButton(GamePadMap.BUTTON_LEFT, "BUTTON_6"); + } + + override public function setupAND():void + { + var stick:StickController; + var button:ButtonController; + + stick = _gamepad.registerStick(GamePadMap.STICK_LEFT,"AXIS_0", "AXIS_1"); + stick.threshold = 0.2; + + stick = _gamepad.registerStick(GamePadMap.STICK_RIGHT,"AXIS_11", "AXIS_14"); + stick.threshold = 0.2; + + _gamepad.registerButton(GamePadMap.L1,"BUTTON_102"); + _gamepad.registerButton(GamePadMap.R1, "BUTTON_103"); + + _gamepad.registerButton(GamePadMap.L2, "AXIS_17"); + _gamepad.registerButton(GamePadMap.R2, "AXIS_18"); + + _gamepad.registerButton(GamePadMap.L3, "BUTTON_106"); + _gamepad.registerButton(GamePadMap.R3, "BUTTON_107"); + + _gamepad.registerButton(GamePadMap.START, "BUTTON_108"); + + button = _gamepad.registerButton(GamePadMap.DPAD_UP, "AXIS_16", "up"); + button.inverted = true; + + _gamepad.registerButton(GamePadMap.DPAD_DOWN,"AXIS_16","down"); + _gamepad.registerButton(GamePadMap.DPAD_RIGHT, "AXIS_15", "right"); + + button = _gamepad.registerButton(GamePadMap.DPAD_LEFT, "AXIS_15", "left"); + button.inverted = true; + + _gamepad.registerButton(GamePadMap.BUTTON_TOP, "BUTTON_100"); + _gamepad.registerButton(GamePadMap.BUTTON_RIGHT, "BUTTON_97"); + _gamepad.registerButton(GamePadMap.BUTTON_BOTTOM, "BUTTON_96"); + _gamepad.registerButton(GamePadMap.BUTTON_LEFT, "BUTTON_99"); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/starling/ScreenTouch.as b/src/citrus/input/controllers/starling/ScreenTouch.as new file mode 100644 index 00000000..b759ba6b --- /dev/null +++ b/src/citrus/input/controllers/starling/ScreenTouch.as @@ -0,0 +1,86 @@ +package citrus.input.controllers.starling +{ + + import citrus.input.InputController; + import citrus.view.starlingview.StarlingView; + import starling.display.DisplayObject; + import starling.display.Sprite; + import starling.events.Touch; + import starling.events.TouchEvent; + import starling.events.TouchPhase; + + + /** + * ScreenTouch is a small InputController to get a starling touch into the input system : + * the common use case is if you want your hero to react on the touch of a screen and handle that + * in the hero's update loop without having to change your code, for example having ScreenTouch with + * "jump" for touchAction, let's you touch the touchTarget(the state by default) and make your Hero jump + * with no changes to Hero's code as it will respond to justDid("jump"). + */ + public class ScreenTouch extends InputController + { + + protected var _touchTarget:DisplayObject; + /** + * touch action is the action triggered on touch, it is jump by default. + */ + public var touchAction:String = "jump"; + + public function ScreenTouch(name:String, params:Object = null) + { + super(name, params); + + if (!_touchTarget) + _touchTarget = ((_ce.state.view as StarlingView).viewRoot as Sprite); + + _touchTarget.addEventListener(TouchEvent.TOUCH, _handleTouch); + } + + private function _handleTouch(e:TouchEvent):void + { + var t:Touch = e.getTouch(_touchTarget); + if (t) + { + switch (t.phase) { + + case TouchPhase.BEGAN: + triggerCHANGE(touchAction, 1, null, defaultChannel); + e.stopImmediatePropagation(); + break; + case TouchPhase.ENDED: + triggerOFF(touchAction, 0, null, defaultChannel); + e.stopImmediatePropagation(); + break; + } + } + } + + override public function destroy():void { + _touchTarget.removeEventListener(TouchEvent.TOUCH, _handleTouch); + _touchTarget = null; + super.destroy(); + } + + /** + * By default, the touchTarget will be set to the state's viewroot, + * accessible from the state like so: + *
    ((view as StarlingView).viewRoot as Sprite)
    + */ + public function get touchTarget():DisplayObject + { + return _touchTarget; + } + + public function set touchTarget(s:DisplayObject):void + { + if (s != _touchTarget) + { + _touchTarget.removeEventListener(TouchEvent.TOUCH, _handleTouch); + s.addEventListener(TouchEvent.TOUCH, _handleTouch); + _touchTarget = s; + } + } + + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/starling/VirtualButton.as b/src/citrus/input/controllers/starling/VirtualButton.as new file mode 100644 index 00000000..e5526468 --- /dev/null +++ b/src/citrus/input/controllers/starling/VirtualButton.as @@ -0,0 +1,127 @@ +package citrus.input.controllers.starling { + + import citrus.input.controllers.AVirtualButton; + + import starling.core.Starling; + import starling.display.Image; + import starling.events.Touch; + import starling.events.TouchEvent; + import starling.events.TouchPhase; + import starling.textures.Texture; + + import flash.display.BitmapData; + import flash.display.Sprite; + + public class VirtualButton extends AVirtualButton { + + public var graphic:starling.display.Sprite; + // main Sprite container. + + protected var button:Image; + + public var buttonUpTexture:Texture; + public var buttonDownTexture:Texture; + + public function VirtualButton(name:String, params:Object = null) { + graphic = new starling.display.Sprite(); + super(name, params); + _x = _x ? _x : Starling.current.stage.stageWidth - (_margin + 2*_buttonradius)/ Starling.current.contentScaleFactor ; + _y = _y ? _y : Starling.current.stage.stageHeight - 2*_buttonradius/ Starling.current.contentScaleFactor; + + initGraphics(); + } + + override protected function initGraphics():void { + + if (!buttonUpTexture) { + var tempSprite:Sprite = new Sprite(); + var tempBitmapData:BitmapData = new BitmapData(_buttonradius * 2, _buttonradius * 2, true, 0x00FFFFFF); + + tempSprite.graphics.clear(); + tempSprite.graphics.beginFill(0x000000, 0.1); + tempSprite.graphics.drawCircle(_buttonradius, _buttonradius, _buttonradius); + tempSprite.graphics.endFill(); + tempBitmapData.draw(tempSprite); + buttonUpTexture = Texture.fromBitmapData(tempBitmapData,true,false,Starling.current.contentScaleFactor); + tempSprite = null; + tempBitmapData = null; + } + + if (!buttonDownTexture) { + var tempSprite2:Sprite = new Sprite(); + var tempBitmapData2:BitmapData = new BitmapData(_buttonradius * 2, _buttonradius * 2, true, 0x00FFFFFF); + + tempSprite2.graphics.clear(); + tempSprite2.graphics.beginFill(0xEE0000, 0.85); + tempSprite2.graphics.drawCircle(_buttonradius, _buttonradius, _buttonradius); + tempSprite2.graphics.endFill(); + tempBitmapData2.draw(tempSprite2); + buttonDownTexture = Texture.fromBitmapData(tempBitmapData2,true,false,Starling.current.contentScaleFactor); + tempSprite2 = null; + tempBitmapData2 = null; + } + + button = new Image(buttonUpTexture); + button.pivotX = button.pivotY = _buttonradius; + + tempSprite = null; + tempBitmapData = null; + + graphic.x = _x; + graphic.y = _y; + + graphic.addChild(button); + + Starling.current.stage.addChild(graphic); + + graphic.addEventListener(TouchEvent.TOUCH, handleTouch); + } + + private function handleTouch(e:TouchEvent):void { + + var buttonTouch:Touch = e.getTouch(button); + + if (buttonTouch) { + + switch (buttonTouch.phase) { + + case TouchPhase.BEGAN: + (buttonTouch.target as Image).texture = buttonDownTexture; + triggerON(buttonAction, 1, null, buttonChannel); + break; + + case TouchPhase.ENDED: + (buttonTouch.target as Image).texture = buttonUpTexture; + triggerOFF(buttonAction, 0, null, buttonChannel); + break; + } + } + } + + override public function get visible():Boolean + { + return _visible = graphic.visible; + } + + override public function set visible(value:Boolean):void + { + _visible = graphic.visible = value; + } + + override public function destroy():void { + + graphic.removeEventListener(TouchEvent.TOUCH, handleTouch); + + graphic.removeChildren(); + + Starling.current.stage.removeChild(graphic); + + buttonUpTexture.dispose(); + buttonDownTexture.dispose(); + button.dispose(); + + super.destroy(); + } + } + +} \ No newline at end of file diff --git a/src/citrus/input/controllers/starling/VirtualJoystick.as b/src/citrus/input/controllers/starling/VirtualJoystick.as new file mode 100644 index 00000000..0ddb3047 --- /dev/null +++ b/src/citrus/input/controllers/starling/VirtualJoystick.as @@ -0,0 +1,241 @@ +package citrus.input.controllers.starling { + + import citrus.input.controllers.AVirtualJoystick; + + import starling.core.Starling; + import starling.display.Image; + import starling.events.Touch; + import starling.events.TouchEvent; + import starling.events.TouchPhase; + import starling.textures.Texture; + + import flash.display.BitmapData; + import flash.display.Sprite; + + /** + * Starling Virtual Joystick + * (drawing itself using flash graphics -> bitmapData -> Starling Texture) + */ + public class VirtualJoystick extends AVirtualJoystick + { + public var graphic:starling.display.Sprite; //main Sprite container. + + //separate joystick elements + public var back:Image; + public var knob:Image; + + public function VirtualJoystick(name:String, params:Object = null) + { + graphic = new starling.display.Sprite(); + + super(name, params); + + _innerradius = _radius - _knobradius; + + _x = _x ? _x : 2*_innerradius / Starling.current.contentScaleFactor; + _y = _y ? _y : Starling.current.stage.stageHeight - 2*_innerradius/ Starling.current.contentScaleFactor ; + + initActionRanges(); + initGraphics(); + + _updateEnabled = true; + } + + override protected function initGraphics():void + { + + if (!back) + { + //draw back + var tempSprite:Sprite = new Sprite(); + var tempBitmapData:BitmapData = new BitmapData(_radius * 2, _radius * 2, true, 0x00FFFFFF); + + tempSprite.graphics.beginFill(0x000000, 0.1); + tempSprite.graphics.drawCircle(_radius, _radius, _radius); + tempBitmapData.draw(tempSprite); + + //draw arrows + + var m:int = 15; // margin + var w:int = 30; // width + var h:int = 40; // height + + tempSprite.graphics.clear(); + tempSprite.graphics.beginFill(0x000000, 0.2); + tempSprite.graphics.moveTo(_radius, m); + tempSprite.graphics.lineTo(_radius - w, h); + tempSprite.graphics.lineTo(_radius + w, h); + tempSprite.graphics.endFill(); + tempBitmapData.draw(tempSprite); + + tempSprite.graphics.clear(); + tempSprite.graphics.lineStyle(); + tempSprite.graphics.beginFill(0x000000, 0.2); + tempSprite.graphics.moveTo(_radius, _radius * 2 - m); + tempSprite.graphics.lineTo(_radius - w, _radius * 2 - h); + tempSprite.graphics.lineTo(_radius + w, _radius * 2 - h); + tempSprite.graphics.endFill(); + tempBitmapData.draw(tempSprite); + + tempSprite.graphics.clear(); + tempSprite.graphics.beginFill(0x000000, 0.2); + tempSprite.graphics.moveTo(m, _radius); + tempSprite.graphics.lineTo(h, _radius - w); + tempSprite.graphics.lineTo(h, _radius + w); + tempSprite.graphics.endFill(); + tempBitmapData.draw(tempSprite); + + tempSprite.graphics.clear(); + tempSprite.graphics.beginFill(0x000000, 0.2); + tempSprite.graphics.moveTo(_radius * 2 - m, _radius); + tempSprite.graphics.lineTo(_radius * 2 - h, _radius - w); + tempSprite.graphics.lineTo(_radius * 2 - h, _radius + w); + tempSprite.graphics.endFill(); + tempBitmapData.draw(tempSprite); + + back = new Image(Texture.fromBitmapData(tempBitmapData,true,false,Starling.current.contentScaleFactor)); + + tempSprite = null; + tempBitmapData = null; + } + + if (!knob) + { + //draw knob + var tempSprite2:Sprite = new Sprite(); + var tempBitmapData2:BitmapData = new BitmapData(_radius * 2, _radius * 2, true, 0x00FFFFFF); + + tempSprite2.graphics.clear(); + tempSprite2.graphics.beginFill(0xEE0000, 0.85); + tempSprite2.graphics.drawCircle(_knobradius, _knobradius, _knobradius); + tempBitmapData2 = new BitmapData(_knobradius * 2, _knobradius * 2, true, 0x00FFFFFF); + tempBitmapData2.draw(tempSprite2); + + knob = new Image(Texture.fromBitmapData(tempBitmapData2,true,false,Starling.current.contentScaleFactor)); + + tempSprite2 = null; + tempBitmapData2 = null; + } + + back.alignPivot(); + graphic.addChild(back); + + knob.alignPivot(); + graphic.addChild(knob); + + //move joystick + graphic.alignPivot(); + graphic.x = _x; + graphic.y = _y; + + graphic.alpha = inactiveAlpha; + + //Add graphic + Starling.current.stage.addChild(graphic); + + //Touch Events + graphic.addEventListener(TouchEvent.TOUCH, handleTouch); + } + + private function handleTouch(e:TouchEvent):void + { + var t:Touch = e.getTouch(graphic); + + if (!t) + return; + + t.getLocation(graphic,_realTouchPosition); + + if (t.phase == TouchPhase.ENDED) + { + reset(); + _grabbed = false; + return; + } + + if (t.phase == TouchPhase.BEGAN) + { + _grabbed = true; + _centered = false; + } + + if (!_grabbed) + return; + + handleGrab(_realTouchPosition.x, _realTouchPosition.y); + + } + + //properties for knob tweening. + private var _vx:Number = 0; + private var _vy:Number = 0; + private var _spring:Number = 400; + private var _friction:Number = 0.0005; + + override public function update():void + { + if (visible) + { + //update knob graphic + if (_grabbed) + { + knob.x = _targetPosition.x; + knob.y = _targetPosition.y; + } + else if (!_centered && !((knob.x > -0.5 && knob.x < 0.5) && (knob.y > -0.5 && knob.y < 0.5))) + { + //http://snipplr.com/view/51769/ + _vx += -knob.x * _spring; + _vy += -knob.y * _spring; + + knob.x += (_vx *= _friction); + knob.y += (_vy *= _friction); + } + else + _centered = true; + + if (_grabbed) + graphic.alpha = activeAlpha; + else + graphic.alpha = inactiveAlpha; + + } + } + + override protected function reset():void + { + super.reset(); + graphic.x = _x; + graphic.y = _y; + } + + public function get visible():Boolean + { + return _visible = graphic.visible; + } + + public function set visible(value:Boolean):void + { + graphic.visible = _visible = value; + } + + override public function destroy():void + { + + _xAxisActions = null; + _yAxisActions = null; + + graphic.removeChildren(); + + Starling.current.stage.removeChild(graphic); + + back.dispose(); + knob.dispose(); + graphic.dispose(); + + super.destroy(); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/math/MathUtils.as b/src/citrus/math/MathUtils.as new file mode 100644 index 00000000..20972c94 --- /dev/null +++ b/src/citrus/math/MathUtils.as @@ -0,0 +1,434 @@ +package citrus.math +{ + + import flash.display.DisplayObject; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + public class MathUtils + { + + public static function DistanceBetweenTwoPoints(x1:Number, x2:Number, y1:Number, y2:Number):Number + { + + var dx:Number = x1 - x2; + var dy:Number = y1 - y2; + + return Math.sqrt(dx * dx + dy * dy); + } + + public static function RotateAroundInternalPoint(object:DisplayObject, pointToRotateAround:Point, rotation:Number):void + { + + // Thanks : http://blog.open-design.be/2009/02/05/rotate-a-movieclipdisplayobject-around-a-point/ + + var m:Matrix = object.transform.matrix; + + var point:Point = pointToRotateAround; + point = m.transformPoint(point); + + RotateAroundExternalPoint(object, point, rotation); + } + + public static function RotateAroundExternalPoint(object:DisplayObject, pointToRotateAround:Point, rotation:Number):void + { + + var m:Matrix = object.transform.matrix; + + m.translate(-pointToRotateAround.x, -pointToRotateAround.y); + m.rotate(rotation * (Math.PI / 180)); + m.translate(pointToRotateAround.x, pointToRotateAround.y); + + object.transform.matrix = m; + } + + /** + * Rotates x,y around Origin (like MathVector.rotate() ) + * if resultPoint is define, will set resultPoint to new values, otherwise, it will return a new point. + * @param p flash.geom.Point + * @param a angle in radians + * @return returns a new rotated point. + */ + public static function rotatePoint(x:Number, y:Number, a:Number, resultPoint:Point = null):Point + { + var c:Number = Math.cos(a); + var s:Number = Math.sin(a); + if (resultPoint) + { + resultPoint.setTo(x * c + y * s, -x * s + y * c); + return null; + } + else + return new Point(x * c + y * s, -x * s + y * c); + } + + /** + * Get the linear equation from two points. + * @return an object, m is the slope and b a constant term. + */ + public static function lineEquation(p0:Point, p1:Point):Object + { + + var a:Number = (p1.y - p0.y) / (p1.x - p0.x); + var b:Number = p0.y - a * p0.x; + + return {m: a, b: b}; + } + + /** + * Linear interpolation function + * @param a start value + * @param b end value + * @param ratio interpolation amount + * @return + */ + public static function lerp(a:Number, b:Number, ratio:Number):Number + { + return a + (b - a) * ratio; + } + + /** + * Creates the axis aligned bounding box for a rotated rectangle. + * @param w width of the rotated rectangle + * @param h height of the rotated rectangle + * @param a angle of rotation around the topLeft point in radian + * @return flash.geom.Rectangle + */ + public static function createAABB(x:Number, y:Number, w:Number, h:Number, a:Number = 0):Rectangle + { + + var aabb:Rectangle = new Rectangle(x, y, w, h); + + if (a == 0) + return aabb; + + var c:Number = Math.cos(a); + var s:Number = Math.sin(a); + var cpos:Boolean; + var spos:Boolean; + + if (s < 0) { s = -s; spos = false; } else { spos = true; } + if (c < 0) { c = -c; cpos = false; } else { cpos = true; } + + aabb.width = h * s + w * c; + aabb.height = h * c + w * s; + + if (cpos) + if (spos) + aabb.x -= h * s; + else + aabb.y -= w * s; + else if (spos) + { + aabb.x -= w * c + h * s; + aabb.y -= h * c; + } + else + { + aabb.x -= w * c; + aabb.y -= w * s + h * c; + } + + return aabb; + } + + /** + * Creates the axis aligned bounding box for a rotated rectangle + * and offsetX , offsetY which is simply the x and y position of + * the aabb relative to the rotated rectangle. the rectangle and the offset values are returned through an object. + * such object can be re-used by passing it through the last argument. + * @param w width of the rotated rectangle + * @param h height of the rotated rectangle + * @param a angle of rotation around the topLeft point in radian + * @param aabbdata the object to store the results in. + * @return {rect:flash.geom.Rectangle,offsetX:Number,offsetY:Number} + */ + public static function createAABBData(x:Number, y:Number, w:Number, h:Number, a:Number = 0, aabbdata:Object = null):Object + { + + if (aabbdata == null) + { + aabbdata = {offsetX: 0, offsetY: 0, rect: new Rectangle()}; + } + + aabbdata.rect.setTo(x, y, w, h); + var offX:Number = 0; + var offY:Number = 0; + + if (a == 0) + { + aabbdata.offsetX = 0; + aabbdata.offsetY = 0; + return aabbdata; + } + + var c:Number = Math.cos(a); + var s:Number = Math.sin(a); + var cpos:Boolean; + var spos:Boolean; + + if (s < 0) { s = -s; spos = false; } else { spos = true; } + if (c < 0) { c = -c; cpos = false; } else { cpos = true; } + + aabbdata.rect.width = h * s + w * c; + aabbdata.rect.height = h * c + w * s; + + if (cpos) + if (spos) + offX -= h * s; + else + offY -= w * s; + else if (spos) + { + offX -= w * c + h * s; + offY -= h * c; + } + else + { + offX -= w * c; + offY -= w * s + h * c; + } + + aabbdata.rect.x += aabbdata.offsetX = offX; + aabbdata.rect.y += aabbdata.offsetY = offY; + + return aabbdata; + } + + /** + * check if angle is between angle a and b + * thanks to http://www.xarg.org/2010/06/is-an-angle-between-two-other-angles/ + */ + public static function angleBetween(angle:Number, a:Number, b:Number):Boolean + { + var mod:Number = Math.PI * 2; + angle = (mod + (angle % mod)) % mod; + a = (mod * 100 + a) % mod; + b = (mod * 100 + b) % mod; + if (a < b) + return a <= angle && angle <= b; + return a <= angle || angle <= b; + } + + /** + * Checks for intersection of Segment if asSegments is true. + * Checks for intersection of Lines if asSegments is false. + * + * http://keith-hair.net/blog/2008/08/04/find-intersection-point-of-two-lines-in-as3/ + * + * @param x1 x of point 1 of segment 1 + * @param y1 y of point 1 of segment 1 + * @param x2 x of point 2 of segment 1 + * @param y2 y of point 2 of segment 1 + * @param x3 x of point 3 of segment 2 + * @param y3 y of point 3 of segment 2 + * @param x4 x of point 4 of segment 2 + * @param y4 y of point 4 of segment 2 + * @param asSegments + * @return the intersection point of segment 1 and 2 or null if they don't intersect. + */ + public static function linesIntersection(x1:Number,y1:Number,x2:Number,y2:Number,x3:Number,y3:Number,x4:Number,y4:Number, asSegments:Boolean = true):Point + { + var ip:Point; + var a1:Number, a2:Number, b1:Number, b2:Number, c1:Number, c2:Number; + + a1 = y2 - y1; + b1 = x1 - x2; + c1 = x2 * y1 - x1 * y2; + a2 = y4 - y3; + b2 = x3 - x4; + c2 = x4 * y3 - x3 * y4; + + var denom:Number = a1 * b2 - a2 * b1; + if (denom == 0) + return null; + + ip = new Point(); + ip.x = (b1 * c2 - b2 * c1) / denom; + ip.y = (a2 * c1 - a1 * c2) / denom; + + //--------------------------------------------------- + //Do checks to see if intersection to endpoints + //distance is longer than actual Segments. + //Return null if it is with any. + //--------------------------------------------------- + if (asSegments) + { + if (pow2(ip.x - x2) + pow2(ip.y - y2) > pow2(x1 - x2) + pow2(y1 - y2)) + return null; + if (pow2(ip.x - x1) + pow2(ip.y - y1) > pow2(x1 - x2) + pow2(y1 - y2)) + return null; + if (pow2(ip.x - x4) + pow2(ip.y - y4) > pow2(x3 - x4) + pow2(y3 - y4)) + return null; + if (pow2(ip.x - x3) + pow2(ip.y - y3) > pow2(x3 - x4) + pow2(y3 - y4)) + return null; + } + return ip; + } + + public static function pow2(value:Number):Number + { + return value * value; + } + + public static function clamp01(value:Number):Number + { + return value < 0 ? 0 : (value > 1 ? 1 : value); + } + + /** + * return random int between min and max + */ + public static function randomInt(min:int, max:int):int + { + return Math.floor(Math.random() * (1 + max - min)) + min; + } + + /** + * best fits the rect Rectangle into the into Rectangle, and returns what scale factor applied to into was necessary to do so. + * @param rect + * @param into + * @return + */ + public static function getBestFitRatio(rect:Rectangle, into:Rectangle):Number + { + if (into.height / into.width > rect.height / rect.width) + return into.width / rect.width; + else + return into.height / rect.height; + } + + /** + * use to get the ratio required for one rectangle to fill the other. + * Either the width, the height, or both will fill the into rectangle. + * Useful to make a background take up all the screen space even though the background + * will be cropped if the aspect ratio is not the same. + * @param rect + * @param into + */ + public static function getFillRatio(rect:Rectangle, into:Rectangle):Number + { + if (into.height / into.width > rect.height / rect.width) + return into.height / rect.height; + else + return into.width / rect.width; + } + + /** + * get a random item from an array with an almost uniform distribution of probabilities using randomInt. + * @param arr + * @return + */ + public static function getArrayRandomItem(arr:Array):* + { + return arr[randomInt(0, arr.length-1)]; + } + + /** + * gets the next element in an array based on the currentElement's position, cyclically. + * - so if currentElement is the last element, you'll get the first in the array. + * @param currentElement + * @param array + */ + public static function getNextInArray(currentElement:*, array:Array):* + { + var currIndex:int = array.lastIndexOf(currentElement) + 1; + if (currIndex >= array.length) + currIndex = 0; + return array[currIndex]; + } + + /** + * gets the previous element in an array based on the currentElement's position, cyclically. + * - so if currentElement is the first element, you'll get the last in the array. + * @param currentElement + * @param array + */ + public static function getPreviousInArray(currentElement:*, array:Array):* + { + var currIndex:int = array.lastIndexOf(currentElement) - 1; + if (currIndex < 0) + currIndex = array.length - 1; + return array[currIndex]; + } + + /** + * returns a random color in given range. + * + * @param minLum minimum for the r, g and b values. + * @param maxLum maximum for the r, g and b values. + * @param b32 return color with alpha channel (ARGB) + * @param randAlpha if format is ARGB, shall we set a random alpha value? + * @return + */ + public static function getRandomColor(minLum:uint = 0, maxLum:uint = 0xFF, b32:Boolean = false, randAlpha:Boolean = false):uint + { + maxLum = maxLum > 0xFF ? 0xFF : maxLum; + minLum = minLum > 0xFF ? 0xFF : minLum; + + var r:uint = MathUtils.randomInt(minLum, maxLum); + var g:uint = MathUtils.randomInt(minLum, maxLum); + var b:uint = MathUtils.randomInt(minLum, maxLum); + + if(!b32) + return r << 16 | g << 8 | b; + else { + var a:uint = randAlpha ? MathUtils.randomInt(0, 255) : 255; + return a << 24 | r << 16 | g << 8 | b; + } + } + + /** + * http://snipplr.com/view/12514/as3-interpolate-color/ + * @param fromColor + * @param toColor + * @param t a number from 0 to 1 + * @return + */ + public static function colorLerp(fromColor:uint, toColor:uint, t:Number):uint + { + var q:Number = 1-t; + var fromA:uint = (fromColor >> 24) & 0xFF; + var fromR:uint = (fromColor >> 16) & 0xFF; + var fromG:uint = (fromColor >> 8) & 0xFF; + var fromB:uint = fromColor & 0xFF; + var toA:uint = (toColor >> 24) & 0xFF; + var toR:uint = (toColor >> 16) & 0xFF; + var toG:uint = (toColor >> 8) & 0xFF; + var toB:uint = toColor & 0xFF; + var resultA:uint = fromA*q + toA*t; + var resultR:uint = fromR*q + toR*t; + var resultG:uint = fromG*q + toG*t; + var resultB:uint = fromB*q + toB*t; + var resultColor:uint = resultA << 24 | resultR << 16 | resultG << 8 | resultB; + return resultColor; + } + + public static function abs(num:Number):Number + { + return num < 0 ? -num : num; + } + + //robert penner's formula for a log of variable base + public static function logx(val:Number, base:Number = 10):Number + { + return Math.log(val) / Math.log(base) + } + + /** + * http://www.robertpenner.com/easing/ + * t current time + * b start value + * c change in value + * d duration + */ + + public static function easeInQuad(t:Number, b:Number, c:Number, d:Number):Number {return c*(t/=d)*t + b;} + public static function easeOutQuad(t:Number, b:Number, c:Number, d:Number):Number {return -c *(t/=d)*(t-2) + b;} + public static function easeInCubic(t:Number, b:Number, c:Number, d:Number):Number {return c*(t/=d)*t*t + b;} + public static function easeOutCubic(t:Number, b:Number, c:Number, d:Number):Number {return c*((t=t/d-1)*t*t + 1) + b;} + public static function easeInQuart(t:Number, b:Number, c:Number, d:Number):Number {return c*(t/=d)*t*t*t + b;} + public static function easeOutQuart(t:Number, b:Number, c:Number, d:Number):Number {return -c * ((t=t/d-1)*t*t*t - 1) + b;} + } +} diff --git a/src/citrus/math/MathVector.as b/src/citrus/math/MathVector.as new file mode 100644 index 00000000..3aa9b8d6 --- /dev/null +++ b/src/citrus/math/MathVector.as @@ -0,0 +1,142 @@ +package citrus.math +{ + public class MathVector + { + public var x:Number; + public var y:Number; + + public function MathVector(x:Number=0, y:Number=0) + { + this.x = x; + this.y = y; + } + + public function copy():MathVector + { + return new MathVector(x, y); + } + + public function copyFrom(vector:MathVector):void + { + this.x = vector.x; + this.y = vector.y; + } + + public function setTo(x:Number = 0, y:Number = 0):void + { + this.x = x; + this.y = y; + } + + public function rotate(angle:Number):void + { + var a:Number = angle; + var ca:Number = Math.cos(a); + var sa:Number = Math.sin(a); + var tx:Number = x; + var ty:Number = y; + + x = tx * ca - ty * sa; + y = tx * sa + ty * ca; + } + + public function scaleEquals(value:Number):void + { + x *= value; y *= value; + } + + public function scale(value:Number, result:MathVector = null):MathVector + { + if (result) { + result.x = x * value; + result.y = y * value; + + return result; + } + + return new MathVector(x * value, y * value); + } + + public function normalize():void + { + var l:Number = length; + x /= l; + y /= l; + } + + public function plusEquals(vector:MathVector):void + { + x += vector.x; + y += vector.y; + } + + public function plus(vector:MathVector, result:MathVector = null):MathVector + { + if (result) { + result.x = x + vector.x; + result.y = y + vector.y; + + return result; + } + + return new MathVector(x + vector.x, y + vector.y); + } + + public function minusEquals(vector:MathVector):void + { + x -= vector.x; + y -= vector.y; + } + + public function minus(vector:MathVector, result:MathVector = null):MathVector + { + if (result) { + result.x = x - vector.x; + result.y = y - vector.y; + + return result; + } + + return new MathVector(x - vector.x, y - vector.y); + } + + public function dot(vector:MathVector):Number + { + return (x * vector.x) + (y * vector.y); + } + + public function get angle():Number + { + return Math.atan2(y, x); + } + + public function set angle(value:Number):void + { + var l:Number = length; + var tx:Number = l * Math.cos(value); + var ty:Number = l * Math.sin(value); + x = tx; + y = ty; + } + + public function get length():Number + { + return Math.sqrt((x*x) + (y*y)); + } + + public function set length(value:Number):void + { + this.scaleEquals(value / length); + } + + public function get normal():MathVector + { + return new MathVector(-y, x); + } + + public function toString():String + { + return "[" + x + ", " + y + "]"; + } + } +} diff --git a/src/citrus/math/PolarPoint.as b/src/citrus/math/PolarPoint.as new file mode 100644 index 00000000..1b6ac61d --- /dev/null +++ b/src/citrus/math/PolarPoint.as @@ -0,0 +1,200 @@ +package citrus.math { + + /** + * A simple class to create points with polar coordinates. + * It holds radius and angle of the point in polar coordinates and helps going back and forth to cartesian coordinates. + * The flash point can create a Point from polar coordinates but will lose its polar properties (radius and angle) + * and will only have x and y in cartesiand coordinates when set. + * + * so in your polar coordinates world this PolarPoint class is a solution to keep your polar coordinate data + * and do further computation with them. + * + * /!\ WARNING : if you are going to intensively convert to and from cartesian coordinates, you are bound to lose + * precision . + * + * (may need optimisation on the conversions ?) + */ + public class PolarPoint + { + + //MAIN POLAR COORDINATES + private var _r:Number = 0; // radius + private var _t:Number = 0; // theta (angle) + + //CARTESIAN + private var _cartX:Number = 0; + private var _cartY:Number = 0; + + private var cartupdated:Boolean = true; + + //------------------------------------- + + /** + * Create and return a new PolarPoint + * @param r radius + * @param t angle + * @return + */ + public static function fromPolar(r:Number, t:Number):PolarPoint + { + return new PolarPoint(r, t); + } + + /** + * Create and return new PolarPoint from cartesian coordinates + * @param x + * @param y + * @return + */ + public static function fromCartesian(x:Number, y:Number):PolarPoint + { + var pc:PolarPoint = new PolarPoint(0, 0); + pc.setFromCartesian(x, y); + return pc; + } + + //----------------------------------- + + /** + * Create a new PolarPoint from radius and angle + * @param r radius + * @param t angle in radian + */ + public function PolarPoint(r:Number,t:Number) + { + _r = r; + _t = t % (2 * Math.PI); + updatecartesian(); + } + + /** + * updates cartesian coordinates from polar coordinates. + */ + protected function updatecartesian():void + { + _cartX = _r * Math.cos(_t); + _cartY = _r * Math.sin(_t); + cartupdated = true; + } + + /** + * cartesian position on the X axis. (converted) + */ + public function get cartX():Number + { + if(!cartupdated) + _cartX = _r * Math.cos(_t); + return _cartX; + } + + /** + * cartesian position on the Y axis. (converted) + */ + public function get cartY():Number + { + if(!cartupdated) + _cartY = _r * Math.sin(_t); + return _cartY; + } + + /** + * Radius. + */ + public function get r():Number + { + return _r; + } + + /** + * Angle in radian. + */ + public function get t():Number + { + return _t; + } + + public function set r(value:Number):void + { + cartupdated = false; + _r = value; + } + + public function set t(value:Number):void + { + cartupdated = false; + _t = value; + } + + public function set cartX(value:Number):void + { + cartupdated = true; + _cartX = value; + } + + public function set cartY(value:Number):void + { + cartupdated = true; + _cartY = value; + } + + /** + * returns a new PolarPoint with the same values + * @return + */ + public function clone():PolarPoint + { + var pc:PolarPoint = new PolarPoint(this.r, this.t); + return pc; + } + + /** + * Add a polar point's coordinates to this point by going through the cartesian values. + * @param polarPoint + */ + public function add(polarPoint:PolarPoint):void + { + setFromCartesian(cartX + polarPoint.cartX, cartY + polarPoint.cartY); + updatecartesian(); + } + + /** + * Substract a polar point's coordinates to this point by going through the cartesian values. + * @param polarPoint + */ + public function sub(polarPoint:PolarPoint):void + { + setFromCartesian(cartX - polarPoint.cartX, cartY - polarPoint.cartY); + updatecartesian(); + } + + /** + * set the point's values + * @param r radius + * @param t angle in radian + */ + public function set(r:Number, t:Number):void + { + _r = r; + _t = t; + updatecartesian(); + } + + public function setFromCartesian(x:Number, y:Number):void + { + _r = Math.sqrt((x * x) + (y * y)); + if (x < 0) + { + _t = (Math.atan(y / x) - Math.PI); + }else{ + _t = (Math.atan(y / x)); + } + } + + public function toString():String + { + return "x:" + cartX + " y:" + cartY + " r:" + r + " t:" + t; + } + + } + +} \ No newline at end of file diff --git a/src/citrus/objects/APhysicsObject.as b/src/citrus/objects/APhysicsObject.as new file mode 100644 index 00000000..90218a28 --- /dev/null +++ b/src/citrus/objects/APhysicsObject.as @@ -0,0 +1,219 @@ +package citrus.objects { + + import citrus.core.CitrusObject; + import citrus.view.ICitrusArt; + + import flash.display.MovieClip; + + /** + * An abstract template used by every physics object. + */ + public class APhysicsObject extends CitrusObject { + + protected var _view:* = MovieClip; + protected var _art:ICitrusArt; + protected var _inverted:Boolean = false; + protected var _parallaxX:Number = 1; + protected var _parallaxY:Number = 1; + protected var _animation:String = ""; + protected var _visible:Boolean = true; + protected var _touchable:Boolean = false; + protected var _x:Number = 0; + protected var _y:Number = 0; + protected var _z:Number = 0; + protected var _rotation:Number = 0; + protected var _radius:Number = 0; + + private var _group:uint = 0; + private var _offsetX:Number = 0; + private var _offsetY:Number = 0; + private var _registration:String = "center"; + + public function APhysicsObject(name:String, params:Object = null) { + super(name, params); + } + + /** + * This function will add the physics stuff to the object. It's automatically called when the object is added to the state. + */ + public function addPhysics():void { + } + + /** + * called when the art is created (and loaded if loading is required) + * @param citrusArt the art + */ + public function handleArtReady(citrusArt:ICitrusArt):void { + _art = citrusArt; + } + + /** + * called when the art changes. the argument is the art with its previous content + * so that you can remove event listeners from it for example. + * @param citrusArt the art + */ + public function handleArtChanged(oldArt:ICitrusArt):void { + } + + /** + * You should override this method to extend the functionality of your physics object. This is where you will + * want to do any velocity/force logic. + */ + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + } + + /** + * This method doesn't depend of your application enter frame. Ideally, the time between two calls never change. + * In this method you will apply any velocity/force logic. + */ + public function fixedUpdate():void { + + } + + /** + * Destroy your physics objects! + */ + override public function destroy():void { + _art = null; + super.destroy(); + } + + /** + * Used for abstraction on body. There is also a getter on the body defined by each engine to keep body's type. + */ + public function getBody():* { + return null; + } + + /** + * The view can be a class, a string to a file, or a display object. It must be supported by the view you target. + */ + public function get view():* + { + return _view; + } + + [Inspectable(defaultValue="",format="File",type="String")] + public function set view(value:*):void + { + _view = value; + } + + /** + * @inheritDoc + */ + public function get art():ICitrusArt + { + return _art; + } + + /** + * Used to invert the view on the y-axis, number of animations friendly! + */ + public function get inverted():Boolean { + return _inverted; + } + + /** + * Animations management works the same way than label whether it uses MovieClip, SpriteSheet or whatever. + */ + public function get animation():String { + return _animation; + } + + public function set animation(value:String):void { + _animation = value; + } + + /** + * You can easily change if an object is visible or not. It hasn't any impact on physics computation. + */ + public function get visible():Boolean { + return _visible; + } + + public function set visible(value:Boolean):void { + _visible = value; + } + + public function get parallaxX():Number { + return _parallaxX; + } + + [Inspectable(defaultValue="1")] + public function set parallaxX(value:Number):void { + _parallaxX = value; + } + + public function get parallaxY():Number { + return _parallaxY; + } + + public function get touchable():Boolean + { + return _touchable; + } + + [Inspectable(defaultValue="false")] + public function set touchable(value:Boolean):void + { + _touchable = value; + } + + [Inspectable(defaultValue="1")] + public function set parallaxY(value:Number):void { + _parallaxY = value; + } + + /** + * The group is similar to a z-index sorting. Default is 0, 1 is over. + */ + public function get group():uint { + return _group; + } + + [Inspectable(defaultValue="0")] + public function set group(value:uint):void { + _group = value; + } + + /** + * offsetX allows to move graphics on x axis compared to their initial point. + */ + public function get offsetX():Number { + return _offsetX; + } + + [Inspectable(defaultValue="0")] + public function set offsetX(value:Number):void { + _offsetX = value; + } + + /** + * offsetY allows to move graphics on y axis compared to their initial point. + */ + public function get offsetY():Number { + return _offsetY; + } + + [Inspectable(defaultValue="0")] + public function set offsetY(value:Number):void { + _offsetY = value; + } + + /** + * Flash registration point is topLeft, whereas physics engine use mostly center. + * You can change the registration point thanks to this property. + */ + public function get registration():String { + return _registration; + } + + [Inspectable(defaultValue="center",enumeration="center,topLeft")] + public function set registration(value:String):void { + _registration = value; + } + } +} diff --git a/src/citrus/objects/Box2DObjectPool.as b/src/citrus/objects/Box2DObjectPool.as new file mode 100644 index 00000000..cdd612c4 --- /dev/null +++ b/src/citrus/objects/Box2DObjectPool.as @@ -0,0 +1,117 @@ +package citrus.objects +{ + + import citrus.core.CitrusEngine; + import citrus.core.citrus_internal; + import citrus.datastructures.DoublyLinkedListNode; + import citrus.datastructures.PoolObject; + import citrus.view.ACitrusView; + import citrus.view.ICitrusArt; + import flash.utils.describeType; + + public class Box2DObjectPool extends PoolObject + { + use namespace citrus_internal; + private static var activationQueue:Vector.; + + public function Box2DObjectPool(pooledType:Class,defaultParams:Object, poolGrowthRate:uint = 1) + { + super(pooledType, defaultParams, poolGrowthRate, true); + + if (!(describeType(pooledType).factory.extendsClass.(@type == "citrus.objects::Box2DPhysicsObject").length() > 0)) + throw new Error("Box2DPoolObject: " + String(pooledType) + " is not a Box2DPhysicsObject"); + + if(!activationQueue) + activationQueue = new Vector.(); + + } + + override protected function _create(node:DoublyLinkedListNode, params:Object = null):void + { + if (!params) + params = { }; + else if (_defaultParams) + { + if (params["width"] != _defaultParams["width"]) + { + trace(this, "you cannot change the default width of your object."); + params["width"] = _defaultParams["width"]; + } + if (params["height"] != _defaultParams["height"]) + { + trace(this, "you cannot change the default height of your object."); + params["height"] = _defaultParams["height"]; + } + } + params["type"] = "aPhysicsObject"; + node.data = new _poolType("aPoolObject", params); + var bp:Box2DPhysicsObject = node.data as Box2DPhysicsObject; + bp.initialize(params); + onCreate.dispatch(bp, params); + bp.addPhysics(); + bp.body.SetActive(false); + state.view.addArt(bp); + bp.citrus_internal::data["updateCall"] = bp.updateCallEnabled; + bp.citrus_internal::data["updateArt"] = (state.view.getArt(bp) as ICitrusArt).updateArtEnabled; + } + + override protected function _recycle(node:DoublyLinkedListNode, params:Object = null):void + { + var bp:Box2DPhysicsObject = node.data as Box2DPhysicsObject; + + activationQueue.unshift( { object:bp, activate:true , func:function():void { + bp.initialize(params); + if ("pauseAnimation" in bp.view) + bp.view.pauseAnimation(true); + bp.visible = true; + bp.updateCallEnabled = bp.citrus_internal::data["updateCall"] as Boolean; + (state.view.getArt(bp) as ICitrusArt).updateArtEnabled = bp.citrus_internal::data["updateArt"] as Boolean; + superRecycle(node,params); + }}); + } + + protected function superRecycle(node:DoublyLinkedListNode,params:Object):void { super._recycle(node,params);} + + override protected function _dispose(node:DoublyLinkedListNode):void + { + var bp:Box2DPhysicsObject = node.data as Box2DPhysicsObject; + activationQueue.unshift( { object:bp, activate:false} ); + if ("pauseAnimation" in bp.view) + bp.view.pauseAnimation(false); + bp.visible = false; + bp.updateCallEnabled = false; + (state.view.getArt(bp) as ICitrusArt).updateArtEnabled = false; + super._dispose(node); + (state.view.getArt(bp) as ICitrusArt).update(state.view); + } + + override public function updatePhysics(timeDelta:Number):void + { + super.updatePhysics(timeDelta); + updateBodies(); + } + + private static function updateBodies():void + { + var entry:Object; + + while((entry = activationQueue.pop()) != null) { + entry.object.body.SetActive(entry.activate); + if(entry.activate) entry.func(); + } + + } + + override protected function _destroy(node:DoublyLinkedListNode):void + { + updateBodies(); + activationQueue.length = 0; + var bp:Box2DPhysicsObject = node.data as Box2DPhysicsObject; + state.view.removeArt(bp); + bp.destroy(); + super._destroy(node); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/objects/Box2DPhysicsObject.as b/src/citrus/objects/Box2DPhysicsObject.as new file mode 100644 index 00000000..1fa954d8 --- /dev/null +++ b/src/citrus/objects/Box2DPhysicsObject.as @@ -0,0 +1,436 @@ +package citrus.objects { + + import Box2D.Collision.b2Manifold; + import Box2D.Collision.Shapes.b2CircleShape; + import Box2D.Collision.Shapes.b2PolygonShape; + import Box2D.Collision.Shapes.b2Shape; + import Box2D.Common.Math.b2Mat22; + import Box2D.Common.Math.b2Transform; + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.b2Body; + import Box2D.Dynamics.b2BodyDef; + import Box2D.Dynamics.b2ContactImpulse; + import Box2D.Dynamics.b2Fixture; + import Box2D.Dynamics.b2FixtureDef; + import Box2D.Dynamics.Contacts.b2Contact; + import citrus.core.CitrusEngine; + import citrus.physics.box2d.Box2D; + import citrus.physics.box2d.IBox2DPhysicsObject; + import citrus.physics.PhysicsCollisionCategories; + import citrus.view.ISpriteView; + + + /** + * You should extend this class to take advantage of Box2D. This class provides template methods for defining + * and creating Box2D bodies, fixtures, shapes, and joints. If you are not familiar with Box2D, you should first + * learn about it via the Box2D Manual. + */ + public class Box2DPhysicsObject extends APhysicsObject implements ISpriteView, IBox2DPhysicsObject + { + protected var _box2D:Box2D; + protected var _bodyDef:b2BodyDef; + protected var _body:b2Body; + protected var _shape:b2Shape; + protected var _fixtureDef:b2FixtureDef; + protected var _fixture:b2Fixture; + + protected var _width:Number = 1; + protected var _height:Number = 1; + + protected var _beginContactCallEnabled:Boolean = false; + protected var _endContactCallEnabled:Boolean = false; + protected var _preContactCallEnabled:Boolean = false; + protected var _postContactCallEnabled:Boolean = false; + + /** + * Used to define vertices' x and y points. + */ + public var points:Array; + protected var _vertices:Array; + + /** + * Creates an instance of a Box2DPhysicsObject. Natively, this object does not default to any graphical representation, + * so you will need to set the "view" property in the params parameter. + */ + public function Box2DPhysicsObject(name:String, params:Object=null) + { + _ce = CitrusEngine.getInstance(); + _box2D = _ce.state.getFirstObjectByType(Box2D) as Box2D; + + super(name, params); + } + + /** + * All your init physics code must be added in this method, no physics code into the constructor. It's automatically called when the object is added to the state. + *

    You'll notice that the Box2DPhysicsObject's initialize method calls a bunch of functions that start with "define" and "create". + * This is how the Box2D objects are created. You should override these methods in your own Box2DPhysicsObject implementation + * if you need additional Box2D functionality. Please see provided examples of classes that have overridden + * the Box2DPhysicsObject.

    + */ + override public function addPhysics():void { + + if (!_box2D) + throw new Error("Cannot create a Box2DPhysicsObject when a Box2D object has not been added to the state."); + + //Override these to customize your Box2D initialization. Things must be done in this order. + defineBody(); + createBody(); + createShape(); + defineFixture(); + createFixture(); + defineJoint(); + createJoint(); + } + + override public function destroy():void + { + _box2D.world.DestroyBody(_body); + _body.SetUserData(null); + _shape = null; + _bodyDef = null; + _fixtureDef = null; + _fixture = null; + _box2D = null; + super.destroy(); + } + + /** + * This method will often need to be overridden to provide additional definition to the Box2D body object. + */ + protected function defineBody():void + { + _bodyDef = new b2BodyDef(); + _bodyDef.type = b2Body.b2_dynamicBody; + _bodyDef.position = new b2Vec2(_x, _y); + _bodyDef.angle = _rotation; + } + + /** + * This method will often need to be overridden to customize the Box2D body object. + */ + protected function createBody():void + { + _body = _box2D.world.CreateBody(_bodyDef); + _body.SetUserData(this); + } + + /** + * This method will often need to be overridden to customize the Box2D shape object. + * The PhysicsObject creates a rectangle by default if the radius it not defined, but you can replace this method's + * definition and instead create a custom shape, such as a line or circle. + */ + protected function createShape():void + { + if (_radius != 0) { + _shape = new b2CircleShape(); + b2CircleShape(_shape).SetRadius(_radius); + } else { + _shape = new b2PolygonShape(); + b2PolygonShape(_shape).SetAsBox(_width / 2, _height / 2); + } + } + + /** + * This method will often need to be overridden to provide additional definition to the Box2D fixture object. + */ + protected function defineFixture():void + { + _fixtureDef = new b2FixtureDef(); + _fixtureDef.shape = _shape; + _fixtureDef.density = 1; + _fixtureDef.friction = 0.6; + _fixtureDef.restitution = 0.3; + _fixtureDef.filter.categoryBits = PhysicsCollisionCategories.Get("Level"); + _fixtureDef.filter.maskBits = PhysicsCollisionCategories.GetAll(); + + // Used by the Tiled Map Editor software, if we defined a polygon/polyline + if (points && points.length > 1) { + + _createVerticesFromPoint(); + + var polygonShape:b2PolygonShape; + var verticesLength:uint = _vertices.length; + for (var i:uint = 0; i < verticesLength; ++i) { + polygonShape = new b2PolygonShape(); + polygonShape.SetAsArray(_vertices[i]); + _fixtureDef.shape = polygonShape; + + _body.CreateFixture(_fixtureDef); + } + } + } + + /** + * This method will often need to be overridden to customize the Box2D fixture object. + */ + protected function createFixture():void + { + _fixture = _body.CreateFixture(_fixtureDef); + } + + /** + * This method will often need to be overridden to provide additional definition to the Box2D joint object. + * A joint is not automatically created, because joints require two bodies. Therefore, if you need to create a joint, + * you will also need to create additional bodies, fixtures and shapes, and then also instantiate a new b2JointDef + * and b2Joint object. + */ + protected function defineJoint():void + { + + } + + /** + * This method will often need to be overridden to customize the Box2D joint object. + * A joint is not automatically created, because joints require two bodies. Therefore, if you need to create a joint, + * you will also need to create additional bodies, fixtures and shapes, and then also instantiate a new b2JointDef + * and b2Joint object. + */ + protected function createJoint():void + { + + } + + /** + * Override this method to handle the begin contact collision. + */ + public function handleBeginContact(contact:b2Contact):void { + + } + + /** + * Override this method to handle the end contact collision. + */ + public function handleEndContact(contact:b2Contact):void { + + } + + /** + * Override this method if you want to perform some actions before the collision (deactivate). + */ + public function handlePreSolve(contact:b2Contact, oldManifold:b2Manifold):void { + + } + + /** + * Override this method if you want to perform some actions after the collision. + */ + public function handlePostSolve(contact:b2Contact, impulse:b2ContactImpulse):void { + + } + + protected function _createVerticesFromPoint():void { + + _vertices = []; + var vertices:Array = []; + + var len:uint = points.length; + for (var i:uint = 0; i < len; ++i) { + vertices.push(new b2Vec2(points[i].x / _box2D.scale, points[i].y / _box2D.scale)); + } + _vertices.push(vertices); + vertices = []; + } + + public function get x():Number + { + if (_body) + return _body.GetPosition().x * _box2D.scale; + else + return _x * _box2D.scale; + } + + public function set x(value:Number):void + { + _x = value / _box2D.scale; + + if (_body) + { + var pos:b2Vec2 = _body.GetPosition(); + pos.x = _x; + _body.SetTransform(new b2Transform(pos, b2Mat22.FromAngle(_body.GetAngle()))); + } + } + + public function get y():Number + { + if (_body) + return _body.GetPosition().y * _box2D.scale; + else + return _y * _box2D.scale; + } + + public function set y(value:Number):void + { + _y = value / _box2D.scale; + + if (_body) + { + var pos:b2Vec2 = _body.GetPosition(); + pos.y = _y; + _body.SetTransform(new b2Transform(pos, b2Mat22.FromAngle(_body.GetAngle()))); + } + } + + public function get z():Number { + return 0; + } + + public function get rotation():Number + { + if (_body) + return _body.GetAngle() * 180 / Math.PI; + else + return _rotation * 180 / Math.PI; + } + + public function set rotation(value:Number):void + { + _rotation = value * Math.PI / 180; + + if (_body) + { + var tr:b2Transform = _body.GetTransform(); + tr.R = b2Mat22.FromAngle(_rotation); + _body.SetTransform(tr); + } + } + + /** + * This can only be set in the constructor parameters. + */ + public function get width():Number + { + return _width * _box2D.scale; + } + + public function set width(value:Number):void + { + _width = value / _box2D.scale; + + if (_initialized && !hideParamWarnings) + trace("Warning: You cannot set " + this + " width after it has been created. Please set it in the constructor."); + } + + /** + * This can only be set in the constructor parameters. + */ + public function get height():Number + { + return _height * _box2D.scale; + } + + public function set height(value:Number):void + { + _height = value / _box2D.scale; + + if (_initialized && !hideParamWarnings) + trace("Warning: You cannot set " + this + " height after it has been created. Please set it in the constructor."); + } + + /** + * No depth in a 2D Physics world. + */ + public function get depth():Number { + return 0; + } + + /** + * This can only be set in the constructor parameters. + */ + public function get radius():Number + { + return _radius * _box2D.scale; + } + + /** + * The object has a radius or a width and height. It can't have both. + */ + [Inspectable(defaultValue="0")] + public function set radius(value:Number):void + { + _radius = value / _box2D.scale; + + if (_initialized) + { + trace("Warning: You cannot set " + this + " radius after it has been created. Please set it in the constructor."); + } + } + + /** + * A direct reference to the Box2D body associated with this object. + */ + public function get body():b2Body { + return _body; + } + + override public function getBody():* + { + return _body; + } + + public function get velocity():Array { + return [_body.GetLinearVelocity().x, _body.GetLinearVelocity().y, 0]; + } + + public function set velocity(value:Array):void { + _body.SetLinearVelocity(new b2Vec2(value[0], value[1])); + } + + /** + * This flag determines if the handleBeginContact method is called or not. Default is false, it saves some performances. + */ + public function get beginContactCallEnabled():Boolean { + return _beginContactCallEnabled; + } + + /** + * Enable or disable the handleBeginContact method to be called. It doesn't change physics behavior. + */ + public function set beginContactCallEnabled(beginContactCallEnabled:Boolean):void { + _beginContactCallEnabled = beginContactCallEnabled; + } + + /** + * This flag determines if the handleEndContact method is called or not. Default is false, it saves some performances. + */ + public function get endContactCallEnabled():Boolean { + return _endContactCallEnabled; + } + + /** + * Enable or disable the handleEndContact method to be called. It doesn't change physics behavior. + */ + public function set endContactCallEnabled(value:Boolean):void { + _endContactCallEnabled = value; + } + + /** + * This flag determines if the handlePreSolve method is called or not. Default is false, it saves some performances. + */ + public function get preContactCallEnabled():Boolean { + return _preContactCallEnabled; + } + + /** + * Enable or disable the handlePreSolve method to be called. It doesn't change physics behavior. + */ + public function set preContactCallEnabled(value:Boolean):void { + _preContactCallEnabled = value; + } + + /** + * This flag determines if the handlePostSolve method is called or not. Default is false, it saves some performances. + */ + public function get postContactCallEnabled():Boolean { + return _postContactCallEnabled; + } + + /** + * Enable or disable the handlePostSolve method to be called. It doesn't change physics behavior. + */ + public function set postContactCallEnabled(value:Boolean):void { + _postContactCallEnabled = value; + } + + } +} \ No newline at end of file diff --git a/src/citrus/objects/CitrusObjectPool.as b/src/citrus/objects/CitrusObjectPool.as new file mode 100644 index 00000000..5b134804 --- /dev/null +++ b/src/citrus/objects/CitrusObjectPool.as @@ -0,0 +1,45 @@ +package citrus.objects +{ + + import citrus.core.CitrusObject; + import citrus.datastructures.DoublyLinkedListNode; + import citrus.datastructures.PoolObject; + import flash.utils.describeType; + + /** + * Base CitrusObject PoolObject (ex: CitrusSprites) + */ + public class CitrusObjectPool extends PoolObject + { + + public function CitrusObjectPool(pooledType:Class,defaultParams:Object, poolGrowthRate:uint = 1) + { + super(pooledType, defaultParams, poolGrowthRate, true); + + if (!(describeType(pooledType).factory.extendsClass.(@type == "citrus.core::CitrusObject").length() > 0)) + throw new Error("CitrusObjectPool: " + String(pooledType) + " is not a CitrusObject"); + } + + override protected function _create(node:DoublyLinkedListNode, params:Object = null):void + { + var co:CitrusObject = node.data = new _poolType("aPoolObject", params); + co.initialize(params); + onCreate.dispatch(co, params); + } + + override protected function _recycle(node:DoublyLinkedListNode, params:Object = null):void + { + var co:CitrusObject = node.data as CitrusObject; + co.initialize(params); + super._recycle(node, params); + } + + override protected function _destroy(node:DoublyLinkedListNode):void + { + var co:CitrusObject = node.data as CitrusObject; + co.destroy(); + super._destroy(node); + } + } + +} \ No newline at end of file diff --git a/src/citrus/objects/CitrusSprite.as b/src/citrus/objects/CitrusSprite.as new file mode 100644 index 00000000..6dafc1e7 --- /dev/null +++ b/src/citrus/objects/CitrusSprite.as @@ -0,0 +1,296 @@ +package citrus.objects { + + import citrus.core.CitrusObject; + import citrus.math.MathVector; + import citrus.view.ICitrusArt; + import citrus.view.ISpriteView; + import citrus.view.spriteview.SpriteDebugArt; + + import org.osflash.signals.Signal; + + import flash.utils.Dictionary; + + /** + * This is the primary class for creating graphical game objects. + * You should override this class to create a visible game object such as a Spaceship, Hero, or Backgrounds. This is the equivalent + * of the Flash Sprite. It has common properties that are required for properly displaying and + * positioning objects. You can also add your logic to this sprite. + * + *

    With a CitrusSprite, there is only simple collision and velocity logic. If you'd like to take advantage of Box2D or Nape physics, + * you should extend the APhysicsObject class instead.

    + */ + public class CitrusSprite extends CitrusObject implements ISpriteView + { + public var collisions:Dictionary = new Dictionary(); + + public var onCollide:Signal = new Signal(CitrusSprite, CitrusSprite, MathVector, Number); + public var onPersist:Signal = new Signal(CitrusSprite, CitrusSprite, MathVector); + public var onSeparate:Signal = new Signal(CitrusSprite, CitrusSprite); + + protected var _x:Number = 0; + protected var _y:Number = 0; + protected var _width:Number = 30; + protected var _height:Number = 30; + protected var _velocity:MathVector = new MathVector(); + protected var _parallaxX:Number = 1; + protected var _parallaxY:Number = 1; + protected var _rotation:Number = 0; + protected var _group:uint = 0; + protected var _visible:Boolean = true; + protected var _touchable:Boolean = false; + protected var _view:* = SpriteDebugArt; + protected var _art:ICitrusArt; + protected var _inverted:Boolean = false; + protected var _animation:String = ""; + protected var _offsetX:Number = 0; + protected var _offsetY:Number = 0; + protected var _registration:String = "topLeft"; + + public function CitrusSprite(name:String, params:Object = null) + { + + super(name, params); + } + + /** + * @inheritDoc + */ + public function handleArtReady(citrusArt:ICitrusArt):void { + _art = citrusArt; + } + + /** + * @inheritDoc + */ + public function handleArtChanged(oldArt:ICitrusArt):void { + } + + override public function destroy():void + { + onCollide.removeAll(); + onPersist.removeAll(); + onSeparate.removeAll(); + collisions = null; + _art = null; + + super.destroy(); + } + + /** + * No physics here, return null. + */ + public function getBody():* { + return null; + } + + public function get x():Number + { + return _x; + } + + public function set x(value:Number):void + { + _x = value; + } + + public function get y():Number + { + return _y; + } + + public function set y(value:Number):void + { + _y = value; + } + + public function get z():Number { + return 0; + } + + public function get width():Number + { + return _width; + } + + public function set width(value:Number):void + { + _width = value; + } + + public function get height():Number + { + return _height; + } + + public function set height(value:Number):void + { + _height = value; + } + + public function get depth():Number { + return 0; + } + + public function get velocity():Array { + return [_velocity.x, _velocity.y, 0]; + } + + public function set velocity(value:Array):void { + + _velocity.x = value[0]; + _velocity.y = value[1]; + } + + public function get parallaxX():Number + { + return _parallaxX; + } + + [Inspectable(defaultValue="1")] + public function set parallaxX(value:Number):void + { + _parallaxX = value; + } + + public function get parallaxY():Number + { + return _parallaxY; + } + + [Inspectable(defaultValue="1")] + public function set parallaxY(value:Number):void + { + _parallaxY = value; + } + + public function get rotation():Number + { + return _rotation; + } + + public function set rotation(value:Number):void + { + _rotation = value; + } + + /** + * The group is similar to a z-index sorting. Default is 0, 1 is over. + */ + public function get group():uint + { + return _group; + } + + [Inspectable(defaultValue="0")] + public function set group(value:uint):void + { + _group = value; + } + + public function get visible():Boolean + { + return _visible; + } + + public function set visible(value:Boolean):void + { + _visible = value; + } + + public function get touchable():Boolean + { + return _touchable; + } + + public function set touchable(value:Boolean):void + { + _touchable = value; + } + + /** + * The view can be a class, a string to a file, or a display object. It must be supported by the view you target. + */ + public function get view():* + { + return _view; + } + + [Inspectable(defaultValue="",format="File",type="String")] + public function set view(value:*):void + { + _view = value; + } + + /** + * @inheritDoc + */ + public function get art():ICitrusArt + { + return _art; + } + + /** + * Used to invert the view on the y-axis, number of animations friendly! + */ + public function get inverted():Boolean + { + return _inverted; + } + + public function set inverted(value:Boolean):void + { + _inverted = value; + } + + public function get animation():String + { + return _animation; + } + + public function set animation(value:String):void + { + _animation = value; + } + + public function get offsetX():Number + { + return _offsetX; + } + + [Inspectable(defaultValue="0")] + public function set offsetX(value:Number):void + { + _offsetX = value; + } + + public function get offsetY():Number + { + return _offsetY; + } + + [Inspectable(defaultValue="0")] + public function set offsetY(value:Number):void + { + _offsetY = value; + } + + public function get registration():String + { + return _registration; + } + + [Inspectable(defaultValue="topLeft",enumeration="center,topLeft")] + public function set registration(value:String):void + { + _registration = value; + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + x += (_velocity.x * timeDelta); + y += (_velocity.y * timeDelta); + } + } +} \ No newline at end of file diff --git a/src/citrus/objects/CitrusSpritePool.as b/src/citrus/objects/CitrusSpritePool.as new file mode 100644 index 00000000..9659a722 --- /dev/null +++ b/src/citrus/objects/CitrusSpritePool.as @@ -0,0 +1,74 @@ +package citrus.objects +{ + + import citrus.core.CitrusEngine; + import citrus.core.citrus_internal; + import citrus.datastructures.DoublyLinkedListNode; + import citrus.datastructures.PoolObject; + import citrus.view.ACitrusView; + import citrus.view.ICitrusArt; + + public class CitrusSpritePool extends PoolObject + { + use namespace citrus_internal; + public function CitrusSpritePool(pooledType:Class,defaultParams:Object, poolGrowthRate:uint = 1) + { + super(pooledType, defaultParams, poolGrowthRate, true); + + //test if defined pooledType class inherits from CitrusSprite + var test:Object; + if ((test = new pooledType("test")) is CitrusSprite) + { test.kill = true; test = null; } + else + throw new Error("CitrusSpritePool: " + String(pooledType) + " is not a CitrusSprite"); + } + + override protected function _create(node:DoublyLinkedListNode, params:Object = null):void + { + if (!params) + params = { }; + + var cs:CitrusSprite = node.data = new _poolType("aPoolObject", params) as CitrusSprite; + cs.initialize(params); + onCreate.dispatch((node.data as _poolType), params); + state.view.addArt(cs); + + cs.citrus_internal::data["updateCall"] = cs.updateCallEnabled; + cs.citrus_internal::data["updateArt"] = (state.view.getArt(cs) as ICitrusArt).updateArtEnabled; + } + + override protected function _recycle(node:DoublyLinkedListNode, params:Object = null):void + { + var cs:CitrusSprite = node.data as CitrusSprite; + cs.initialize(params); + if ("pauseAnimation" in cs.view) + cs.view.pauseAnimation(true); + cs.visible = true; + cs.updateCallEnabled = cs.citrus_internal::data["updateCall"] as Boolean; + (state.view.getArt(cs) as ICitrusArt).updateArtEnabled = cs.citrus_internal::data["updateArt"] as Boolean; + super._recycle(node, params); + } + + override protected function _dispose(node:DoublyLinkedListNode):void + { + var cs:CitrusSprite = node.data as CitrusSprite; + if ("pauseAnimation" in cs.view) + cs.view.pauseAnimation(false); + cs.visible = false; + cs.updateCallEnabled = false; + (state.view.getArt(cs) as ICitrusArt).updateArtEnabled = false; + super._dispose(node); + (state.view.getArt(cs) as ICitrusArt).update(state.view); + } + + override protected function _destroy(node:DoublyLinkedListNode):void + { + var cs:CitrusSprite = node.data as CitrusSprite; + state.view.removeArt(cs); + cs.destroy(); + super._destroy(node); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/objects/NapeObjectPool.as b/src/citrus/objects/NapeObjectPool.as new file mode 100644 index 00000000..3fc6d658 --- /dev/null +++ b/src/citrus/objects/NapeObjectPool.as @@ -0,0 +1,92 @@ +package citrus.objects +{ + + import citrus.core.CitrusEngine; + import citrus.core.citrus_internal; + import citrus.datastructures.DoublyLinkedListNode; + import citrus.datastructures.PoolObject; + import citrus.physics.nape.Nape; + import citrus.view.ACitrusView; + import citrus.view.ICitrusArt; + import flash.utils.describeType; + + public class NapeObjectPool extends PoolObject + { + use namespace citrus_internal; + public function NapeObjectPool(pooledType:Class,defaultParams:Object, poolGrowthRate:uint = 1) + { + super(pooledType, defaultParams, poolGrowthRate, true); + + if (!(describeType(pooledType).factory.extendsClass.(@type == "citrus.objects::NapePhysicsObject").length() > 0)) + throw new Error("NapePoolObject: " + String(pooledType) + " is not a NapePhysicsObject"); + + } + + override protected function _create(node:DoublyLinkedListNode, params:Object = null):void + { + if (!params) + params = { }; + else if (_defaultParams) + { + if (params["width"] != _defaultParams["width"]) + { + trace(this, "you cannot change the default width of your object."); + params["width"] = _defaultParams["width"]; + } + if (params["height"] != _defaultParams["height"]) + { + trace(this, "you cannot change the default height of your object."); + params["height"] = _defaultParams["height"]; + } + } + params["type"] = "aPhysicsObject"; + node.data = new _poolType("aPoolObject", params); + var np:NapePhysicsObject = node.data as NapePhysicsObject; + np.initialize(params); + onCreate.dispatch((node.data as _poolType), params); + np.addPhysics(); + np.body.space = null; + state.view.addArt(np); + + np.citrus_internal::data["updateCall"] = np.updateCallEnabled; + + np.citrus_internal::data["updateArt"] = (state.view.getArt(np) as ICitrusArt).updateArtEnabled; + } + + override protected function _recycle(node:DoublyLinkedListNode, params:Object = null):void + { + var np:NapePhysicsObject = node.data as NapePhysicsObject; + np.initialize(params); + np.body.space = (state.getFirstObjectByType(Nape) as Nape).space; + if ("pauseAnimation" in np.view) + np.view.pauseAnimation(true); + np.visible = true; + np.updateCallEnabled = np.citrus_internal::data["updateCall"] as Boolean; + (state.view.getArt(np) as ICitrusArt).updateArtEnabled = np.citrus_internal::data["updateArt"] as Boolean; + super._recycle(node, params); + } + + override protected function _dispose(node:DoublyLinkedListNode):void + { + var np:NapePhysicsObject = node.data as NapePhysicsObject; + np.body.space = null; + if ("pauseAnimation" in np.view) + np.view.pauseAnimation(false); + np.visible = false; + np.updateCallEnabled = false; + (state.view.getArt(np) as ICitrusArt).updateArtEnabled = false; + super._dispose(node); + (state.view.getArt(np) as ICitrusArt).update(state.view); + } + + override protected function _destroy(node:DoublyLinkedListNode):void + { + var np:NapePhysicsObject = node.data as NapePhysicsObject; + state.view.removeArt(np); + np.destroy(); + super._destroy(node); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/objects/NapePhysicsObject.as b/src/citrus/objects/NapePhysicsObject.as new file mode 100644 index 00000000..e3104d44 --- /dev/null +++ b/src/citrus/objects/NapePhysicsObject.as @@ -0,0 +1,367 @@ +package citrus.objects { + + import citrus.physics.nape.INapePhysicsObject; + import citrus.physics.nape.Nape; + import citrus.physics.PhysicsCollisionCategories; + import citrus.view.ISpriteView; + import nape.callbacks.CbType; + import nape.callbacks.InteractionCallback; + import nape.callbacks.PreCallback; + import nape.callbacks.PreFlag; + import nape.dynamics.InteractionFilter; + import nape.geom.GeomPoly; + import nape.geom.GeomPolyList; + import nape.geom.Vec2; + import nape.geom.Vec2List; + import nape.phys.Body; + import nape.phys.BodyType; + import nape.phys.Material; + import nape.shape.Circle; + import nape.shape.Polygon; + import nape.shape.Shape; + import nape.shape.ValidationResult; + + + /** + * You should extend this class to take advantage of Nape. This class provides template methods for defining + * and creating Nape bodies, fixtures, shapes, and joints. If you are not familiar with Nape, you should first + * learn about it via the Nape Manual. + */ + public class NapePhysicsObject extends APhysicsObject implements ISpriteView, INapePhysicsObject { + + public static const PHYSICS_OBJECT:CbType = new CbType(); + + protected var _nape:Nape; + protected var _bodyType:BodyType; + protected var _body:Body; + protected var _material:Material; + protected var _shape:Shape; + + protected var _width:Number = 30; + protected var _height:Number = 30; + + protected var _beginContactCallEnabled:Boolean = false; + protected var _endContactCallEnabled:Boolean = false; + + /** + * Used to define vertices' x and y points. + */ + public var points:Array; + + /** + * Creates an instance of a NapePhysicsObject. Natively, this object does not default to any graphical representation, + * so you will need to set the "view" property in the params parameter. + */ + public function NapePhysicsObject(name:String, params:Object = null) { + + super(name, params); + } + + /** + * All your init physics code must be added in this method, no physics code into the constructor. It's automatically called when the object is added to the state. + *

    You'll notice that the NapePhysicsObject's initialize method calls a bunch of functions that start with "define" and "create". + * This is how the Nape objects are created. You should override these methods in your own NapePhysicsObject implementation + * if you need additional Nape functionality. Please see provided examples of classes that have overridden + * the NapePhysicsObject.

    + */ + override public function addPhysics():void { + + _nape = _ce.state.getFirstObjectByType(Nape) as Nape; + + if (!_nape) + throw new Error("Cannot create a NapePhysicsObject when a Nape object has not been added to the state."); + + //Override these to customize your Nape initialization. Things must be done in this order. + defineBody(); + createBody(); + createMaterial(); + createShape(); + createFilter(); + createConstraint(); + } + + override public function destroy():void { + _nape.space.bodies.remove(_body); + _body.userData.myData = null; + _body = null; + _material = null; + _shape = null; + _nape = null; + super.destroy(); + } + + public function handlePreContact(callback:PreCallback):PreFlag { + return PreFlag.ACCEPT; + } + + /** + * Override this method to handle the begin contact collision. + */ + public function handleBeginContact(callback:InteractionCallback):void { + } + + /** + * Override this method to handle the end contact collision. + */ + public function handleEndContact(callback:InteractionCallback):void { + } + + /** + * This method will often need to be overridden to provide additional definition to the Nape body object. + */ + protected function defineBody():void { + + _bodyType = BodyType.DYNAMIC; + } + + /** + * This method will often need to be overridden to customize the Nape body object. + */ + protected function createBody():void { + + _body = new Body(_bodyType, Vec2.weak(_x, _y)); + _body.userData.myData = this; + + _body.rotate(Vec2.weak(_x, _y), _rotation); + } + + /** + * This method will often need to be overridden to customize the Nape material object. + */ + protected function createMaterial():void { + + _material = new Material(0.65, 0.57, 1.2, 1, 0); + } + + /** + * This method will often need to be overridden to customize the Nape shape object. + * The PhysicsObject creates a rectangle by default if the radius it not defined, but you can replace this method's + * definition and instead create a custom shape, such as a line or circle. + */ + protected function createShape():void { + + // Used by the Tiled Map Editor software, if we defined a polygon/polyline + if (points && points.length > 1) { + + var verts:Vec2List = new Vec2List(); + + for each (var point:Object in points) + verts.push(Vec2.weak(point.x as Number, point.y as Number)); + + var geomPoly:GeomPoly = new GeomPoly(verts); + var polygon:Polygon = new Polygon(geomPoly, _material); + var validation:ValidationResult = polygon.validity(); + + if (validation == ValidationResult.VALID) + _shape = polygon; + + else if (validation == ValidationResult.CONCAVE) { + + var convex:GeomPolyList = geomPoly.convexDecomposition(); + convex.foreach(function(p:GeomPoly):void { + _body.shapes.add(new Polygon(p)); + }); + + return; + + } else + throw new Error("Invalid polygon/polyline"); + + } else { + + if (_radius != 0) + _shape = new Circle(_radius, null, _material); + else + _shape = new Polygon(Polygon.box(_width, _height), _material); + } + + _body.shapes.add(_shape); + } + + /** + * This method will often need to be overridden to customize the Nape filter object. + */ + protected function createFilter():void { + + _body.setShapeFilters(new InteractionFilter(PhysicsCollisionCategories.Get("Level"), PhysicsCollisionCategories.GetAll())); + } + + /** + * This method will often need to be overridden to customize the Nape constraint object. + */ + protected function createConstraint():void { + + _body.space = _nape.space; + _body.cbTypes.add(PHYSICS_OBJECT); + } + + public function get x():Number + { + if (_body) + return _body.position.x; + else + return _x; + } + + public function set x(value:Number):void + { + _x = value; + + if (_body) + { + var pos:Vec2 = _body.position; + pos.x = _x; + _body.position = pos; + } + } + + public function get y():Number + { + if (_body) + return _body.position.y; + else + return _y; + } + + public function set y(value:Number):void + { + _y = value; + + if (_body) + { + var pos:Vec2 = _body.position; + pos.y = _y; + _body.position = pos; + } + } + + public function get z():Number { + return 0; + } + + public function get rotation():Number + { + if (_body) + return _body.rotation * 180 / Math.PI; + else + return _rotation * 180 / Math.PI; + } + + public function set rotation(value:Number):void + { + _rotation = value * Math.PI / 180; + + if (_body) + _body.rotation = _rotation; + } + + /** + * This can only be set in the constructor parameters. + */ + public function get width():Number + { + return _width; + } + + public function set width(value:Number):void + { + _width = value; + + if (_initialized && !hideParamWarnings) + trace("Warning: You cannot set " + this + " width after it has been created. Please set it in the constructor."); + } + + /** + * This can only be set in the constructor parameters. + */ + public function get height():Number + { + return _height; + } + + public function set height(value:Number):void + { + _height = value; + + if (_initialized && !hideParamWarnings) + trace("Warning: You cannot set " + this + " height after it has been created. Please set it in the constructor."); + } + + /** + * No depth in a 2D Physics world. + */ + public function get depth():Number { + return 0; + } + + /** + * This can only be set in the constructor parameters. + */ + public function get radius():Number + { + return _radius; + } + + /** + * The object has a radius or a width and height. It can't have both. + */ + [Inspectable(defaultValue="0")] + public function set radius(value:Number):void + { + _radius = value; + + if (_initialized) + { + trace("Warning: You cannot set " + this + " radius after it has been created. Please set it in the constructor."); + } + } + + /** + * A direct reference to the Nape body associated with this object. + */ + public function get body():Body { + return _body; + } + + override public function getBody():* + { + return _body; + } + + public function get velocity():Array { + return [_body.velocity.x, _body.velocity.y, 0]; + } + + public function set velocity(value:Array):void { + _body.velocity.setxy(value[0], value[1]); + } + + /** + * This flag determines if the handleBeginContact method is called or not. Default is false, it saves some performances. + */ + public function get beginContactCallEnabled():Boolean { + return _beginContactCallEnabled; + } + + /** + * Enable or disable the handleBeginContact method to be called. It doesn't change physics behavior. + */ + public function set beginContactCallEnabled(beginContactCallEnabled:Boolean):void { + _beginContactCallEnabled = beginContactCallEnabled; + } + + /** + * This flag determines if the handleEndContact method is called or not. Default is false, it saves some performances. + */ + public function get endContactCallEnabled():Boolean { + return _endContactCallEnabled; + } + + /** + * Enable or disable the handleEndContact method to be called. It doesn't change physics behavior. + */ + public function set endContactCallEnabled(endContactCallEnabled:Boolean):void { + _endContactCallEnabled = endContactCallEnabled; + } + } +} diff --git a/src/citrus/objects/common/Emitter.as b/src/citrus/objects/common/Emitter.as new file mode 100644 index 00000000..66894746 --- /dev/null +++ b/src/citrus/objects/common/Emitter.as @@ -0,0 +1,293 @@ +package citrus.objects.common +{ + + import citrus.core.CitrusEngine; + import citrus.core.CitrusObject; + import citrus.view.blittingview.BlittingArt; + import citrus.view.blittingview.BlittingView; + + /** + * An emitter creates particles at a specified rate with specified distribution properties. You can set the emitter's x and y + * location at any time as well as change the graphic of the particles that the emitter makes. + */ + public class Emitter extends CitrusObject + { + /** + * The X position where the particles will emit from. + */ + public var x:Number = 0; + + /** + * The Y position where the particles will emit from. + */ + public var y:Number = 0; + + /** + * In milliseconds, how often the emitter will release new particles. + */ + public var emitFrequency:Number = 300; + + /** + * The number of particles that the emitter will release during each emission. + */ + public var emitAmount:uint = 1; + + /** + * In milliseconds, how long the particles will last before being recycled. + */ + public var particleLifeSpan:Number = 3000; + + /** + * The X force applied to particle velocity, in pixels per frame. + */ + public var gravityX:Number = 0; + + /** + * The Y force applied to particle velocity, in pixels per frame. + */ + public var gravityY:Number = 0; + + /** + * A number between 0 and 1 to create air resistance. Lower numbers create slow floatiness like a feather. + */ + public var dampingX:Number = 1; + + /** + * A number between 0 and 1 to create air resistance. Lower numbers create slow floatiness like a feather. + */ + public var dampingY:Number = 1; + + /** + * The minimum initial impulse velocity that a particle can have via the randomly generated impulse on the X axis. + */ + public var minImpulseX:Number = -10; + + /** + * The maximum initial impulse velocity that a particle can have via the randomly generated impulse on the X axis. + */ + public var maxImpulseX:Number = 10; + + /** + * The minimum initial impulse velocity that a particle can have via the randomly generated impulse on the Y axis. + */ + public var minImpulseY:Number = -10; + + /** + * The maximum initial impulse velocity that a particle can have via the randomly generated impulse on the Y axis. + */ + public var maxImpulseY:Number = 10; + + /** + * In milliseconds, how long the emitter lasts before destroying itself. If the value is -1, it lasts forever. + */ + public var emitterLifeSpan:int = -1; + + /** + * The width deviation from the x position that a particle can be created via a randomly generated number. + */ + public var emitAreaWidth:Number = 0; + + /** + * The height deviation from the y position that a particle can be created via a randomly generated number. + */ + public var emitAreaHeight:Number = 0; + + /** + * The group is similar to a z-index sorting. Default is 0, 1 is over. + */ + public var group:uint = 0; + + private var _particles:Vector. = new Vector.(); + private var _recycle:Array = []; + private var _graphic:*; + private var _particlesCreated:uint = 0; + private var _lastEmission:Number = 0; + private var _birthTime:Number = -1; + + /** + * Makes a particle emitter. + * @param name The name of the emitter. + * @param graphic The graphic class to use to create each particle. + * @param x The X position where the particles will emit from. + * @param y The Y position where the particles will emit from. + * @param emitFrequency In milliseconds, how often the emitter will release new particles. + * @param emitAmount The number of particles that the emitter will release during each emission. + * @param particleLifeSpan In milliseconds, how long the particles will last before being recycled. + * @param gravityX The X force applied to particle velocity, in pixels per frame. + * @param gravityY The Y force applied to particle velocity, in pixels per frame. + * @param dampingX A number between 0 and 1 to create air resistance. Lower numbers create slow floatiness like a feather. + * @param dampingY A number between 0 and 1 to create air resistance. Lower numbers create slow floatiness like a feather. + * @param minImpulseX The minimum initial impulse velocity that a particle can have via the randomly generated impulse on the X axis. + * @param maxImpulseX The maximum initial impulse velocity that a particle can have via the randomly generated impulse on the X axis. + * @param minImpulseY The minimum initial impulse velocity that a particle can have via the randomly generated impulse on the Y axis. + * @param maxImpulseY The maximum initial impulse velocity that a particle can have via the randomly generated impulse on the Y axis. + * @param emitterLifeSpan In milliseconds, how long the emitter lasts before destroying itself. If the value is -1, it lasts forever. + * @param emitAreaWidth The width deviation from the x position that a particle can be created via a randomly generated number. + * @param emitAreaHeight The height deviation from the y position that a particle can be created via a randomly generated number. + * @return An emitter. + */ + public static function Make(name:String, + graphic:*, + x:Number, + y:Number, + emitFrequency:Number, + emitAmount:Number, + particleLifeSpan:Number, + gravityX:Number, + gravityY:Number, + dampingX:Number, + dampingY:Number, + minImpulseX:Number, + maxImpulseX:Number, + minImpulseY:Number, + maxImpulseY:Number, + emitterLifeSpan:Number = -1, + emitAreaWidth:Number = 0, + emitAreaHeight:Number = 0, + group:uint = 0):Emitter + { + return new Emitter(name, { graphic: graphic, x: x, y: y, emitFrequency: emitFrequency, emitAmount: emitAmount, particleLifeSpan: particleLifeSpan, + gravityX: gravityX, gravityY: gravityY, dampingX: dampingX, dampingY: dampingY, minImpulseX: minImpulseX, + maxImpulseX: maxImpulseX, minImpulseY: minImpulseY, maxImpulseY: maxImpulseY, emitterLifeSpan: emitterLifeSpan, + emitAreaWidth: emitAreaWidth, emitAreaHeight: emitAreaHeight, group: group} ); + } + + public function Emitter(name:String, params:Object = null) + { + updateCallEnabled = true; + + super(name, params); + _ce = CitrusEngine.getInstance(); + } + + override public function destroy():void + { + for each (var particle:EmitterParticle in _particles) + particle.kill = true; + _particles.length = 0; + + for each (particle in _recycle) + particle.kill = true; + _recycle.length = 0; + + super.destroy(); + } + + /** + * The graphic that will be generated for each particle. This works just like the CitrusObject's view property. + * The value can be 1) The path to an external image, 2) A DisplayObject class (not an instance) in String notation + * (view: "com.graphics.myParticle") or 3) A DisplayObject class (not an instance) in Object notation + * (view: MyParticle). See the documentation for ISpriteView.view for more info. + */ + public function get graphic():* + { + return _graphic; + } + + public function set graphic(value:*):void + { + _graphic = value; + destroyRecycle(); //clear the reusable ones, they all have to be remade + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + var now:Number = new Date().time; + var particle:EmitterParticle; + var emitterExpired:Boolean = (emitterLifeSpan != -1 && _birthTime != -1 && _birthTime + emitterLifeSpan <= now); + + //check to see if any particles should be destroyed + for (var i:int = _particles.length - 1; i >= 0; i--) + { + particle = _particles[i]; + if (particle.birthTime + particleLifeSpan <= now) + { + if (particle.canRecycle) + { + particle.visible = false; + _recycle.push(particle); + } + else + { + particle.kill = true; + } + _particles.splice(_particles.indexOf(particle), 1); + } + } + + //generate more particles if necessary + if (!emitterExpired && now - _lastEmission >= emitFrequency) + { + _lastEmission = now; + + for (i = 0; i < emitAmount; i++ ) + { + particle = getOrCreateParticle(now); + } + + //Set the emitter's birth time if this is the first emission. + if (_birthTime == -1) + _birthTime = now; + } + + //update positions on existing particles. + for each (particle in _particles) + { + particle.velocityX += gravityX; + particle.velocityY += gravityY; + particle.velocityX *= dampingX; + particle.velocityY *= dampingY; + + particle.x += (particle.velocityX * timeDelta); + particle.y += (particle.velocityY * timeDelta); + } + + //should we destroy the emitter? + if (emitterExpired && _particles.length == 0) + kill = true; + } + + private function getOrCreateParticle(birthTime:Number):EmitterParticle + { + var particle:EmitterParticle = _recycle.pop(); + + if (!particle) + { + if (_ce.state.view is BlittingView) + { + particle = new EmitterParticle(name + "_" + _particlesCreated++, {view: new BlittingArt(graphic)}); + } + else + { + particle = new EmitterParticle(name + "_" + _particlesCreated++, { view: graphic } ); + } + + _ce.state.add(particle); + } + + _particles.push(particle); + particle.x = Math.random() * emitAreaWidth + (x - emitAreaWidth / 2); + particle.y = Math.random() * emitAreaHeight + (y - emitAreaHeight / 2); + particle.velocityX = Math.random() * (maxImpulseX - minImpulseX) + minImpulseX; + particle.velocityY = Math.random() * (maxImpulseY - minImpulseY) + minImpulseY; + particle.birthTime = birthTime; + particle.group = group; + particle.visible = true; + + return particle; + } + + private function destroyRecycle():void + { + for each (var particle:EmitterParticle in _recycle) + particle.kill = true; + _recycle.length = 0; + + for each (particle in _particles) + particle.canRecycle = false; + } + } + +} \ No newline at end of file diff --git a/src/citrus/objects/common/EmitterParticle.as b/src/citrus/objects/common/EmitterParticle.as new file mode 100644 index 00000000..483f153b --- /dev/null +++ b/src/citrus/objects/common/EmitterParticle.as @@ -0,0 +1,22 @@ +package citrus.objects.common +{ + + import citrus.objects.CitrusSprite; + + public class EmitterParticle extends CitrusSprite + { + public var velocityX:Number = 0; + public var velocityY:Number = 0; + public var birthTime:Number = 0; + public var canRecycle:Boolean = true; + + public function EmitterParticle(name:String, params:Object = null) + { + super(name, params); + + if (birthTime == 0) + birthTime = new Date().time; + } + } + +} \ No newline at end of file diff --git a/src/citrus/objects/common/Path.as b/src/citrus/objects/common/Path.as new file mode 100644 index 00000000..33bceeb2 --- /dev/null +++ b/src/citrus/objects/common/Path.as @@ -0,0 +1,107 @@ +package citrus.objects.common { + + import citrus.core.CitrusObject; + import citrus.math.MathVector; + + /** + * This class defines a set of points (MathVector) that can be used with the MovingPlatform or other objects. + * Don't call the State's add method on this object, because you don't want to create a graphic object neither than calling + * an no needed update method. Also don't forget to call yourself the destroy method! + */ + public class Path extends CitrusObject + { + private var _nodes:Vector.; + + private var _isPolygon:Boolean = false; + + public function Path(name:String, params:Object = null) + { + super(name, params); + _nodes = new Vector.; + } + + override public function destroy():void { + + _nodes.length = 0; + + super.destroy(); + } + + /** + * Determines if the path is a continuous polygon. + * Example. can be used to make MovingPlatform to follow certain path in a "circle". + */ + public function get isPolygon():Boolean + { + return _isPolygon; + } + + public function set isPolygon(value:Boolean):void + { + _isPolygon = value; + } + + /** + * Add a new node to the end of the path at the specified location. + */ + public function add(x:Number, y:Number):void + { + _nodes.push(new MathVector(x, y)); + } + + /** + * Sometimes its easier or faster to just pass a point object instead of separate X and Y coordinates. + */ + public function addPoint(value:MathVector):void + { + _nodes.push(value); + } + + /** + * Returns a node from a certain index. + */ + public function getPointAt(index:uint):MathVector + { + if (_nodes.length > index) + { + return _nodes[index] as MathVector; + } + + return null; + } + + /** + * Get the first node in the list. + */ + public function head():MathVector + { + if (_nodes.length > 0) + { + return _nodes[0]; + } + + return null; + } + + /** + * Get the last node in the list. + */ + public function tail():MathVector + { + if (_nodes.length > 0) + { + return _nodes[_nodes.length - 1]; + } + + return null; + } + + /** + * Length of the path in nodes. + */ + public function get length():uint + { + return _nodes.length; + } + } +} diff --git a/src/citrus/objects/complex/box2dstarling/Bridge.as b/src/citrus/objects/complex/box2dstarling/Bridge.as new file mode 100644 index 00000000..6d8bcfef --- /dev/null +++ b/src/citrus/objects/complex/box2dstarling/Bridge.as @@ -0,0 +1,208 @@ +package citrus.objects.complex.box2dstarling { + + import Box2D.Collision.Shapes.b2PolygonShape; + import Box2D.Collision.Shapes.b2Shape; + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.Joints.b2RevoluteJointDef; + import Box2D.Dynamics.b2Body; + import Box2D.Dynamics.b2BodyDef; + import Box2D.Dynamics.b2FixtureDef; + + import citrus.math.MathUtils; + import citrus.objects.Box2DPhysicsObject; + import citrus.objects.CitrusSprite; + import citrus.physics.PhysicsCollisionCategories; + + import starling.display.Image; + import starling.textures.Texture; + import starling.utils.rad2deg; + + /** + * A Bridge consists of Box2dPhysicsObjects connected with RevoluteJoints and is build between two Objects (usually platforms). + * Sometimes you will not properly stand on the bridge because of rotating etc., to solve this modify your Heros's handleBeginContact() function + *
      Properties: + *
    • bridgeLength : If not set it's calculated automatically.
    • + *
    • useTexture : set false for debugging
    • + *
    • segmentBitmapData : BitmapData for creating the texture for Bridgeelements. It get's scaled with keeping + * proportion between width and height
    + */ + public class Bridge extends Box2DPhysicsObject { + + public var leftAnchor:Box2DPhysicsObject; + public var rightAnchor:Box2DPhysicsObject; + + public var bridgeLength:uint; + public var numSegments:uint = 9; + public var heightSegment:uint = 15; + public var useTexture:Boolean = false; + public var density:Number = 1; + public var friction:Number = 1; + public var restitution:Number = 1; + public var segmentTexture:Texture; + + private var widthSegment:uint; + private var ws:Number;// worldscale + private var display:Boolean = false; + + private var _vecBodyDefBridge:Vector.; + private var _vecBodyBridge:Vector.; + private var _vecFixtureDefBridge:Vector.; + private var _vecRevoluteJointDef:Vector.; + private var _shapeSegment:b2Shape; + private var _vecSprites:Vector.; + + public function Bridge(name:String, params:Object = null) { + + updateCallEnabled = true; + + super(name, params); + } + + + override public function destroy():void + { + var i:uint = 0; + for each (var bodyChain:b2Body in _vecBodyBridge) { + _box2D.world.DestroyBody(bodyChain); + _ce.state.remove(_vecSprites[i]); + ++i; + } + super.destroy(); + } + + override public function update(timeDelta:Number):void { + super.update(timeDelta); + + if (display) + updateSegmentDisplay(); + } + + override protected function defineBody():void { + super.defineBody(); + + ws = _box2D.scale; + + if (!bridgeLength) { + var distance:Number = MathUtils.DistanceBetweenTwoPoints(rightAnchor.x - int(rightAnchor.width / 2), leftAnchor.x + int(leftAnchor.width / 2), rightAnchor.y, leftAnchor.y) / 2; + bridgeLength = distance; + } + + widthSegment = bridgeLength / numSegments + if (useTexture) { + initDisplay(); + } + _vecBodyDefBridge = new Vector.(); + var bodyDefChain:b2BodyDef; + for (var i:uint = 0; i < numSegments; ++i) { + bodyDefChain = new b2BodyDef(); + bodyDefChain.type = b2Body.b2_dynamicBody; + bodyDefChain.position.Set(leftAnchor.x / ws + leftAnchor.width / 2 / ws + i * widthSegment / ws - 10 / ws, leftAnchor.y / ws); + _vecBodyDefBridge.push(bodyDefChain); + } + } + + override protected function createBody():void { + super.createBody(); + + _vecBodyBridge = new Vector.(); + var bodyChain:b2Body; + for each (var bodyDefChain:b2BodyDef in _vecBodyDefBridge) { + bodyChain = _box2D.world.CreateBody(bodyDefChain); + bodyChain.SetUserData(this); + _vecBodyBridge.push(bodyChain); + } + } + + override protected function createShape():void { + super.createShape(); + + _shapeSegment = new b2PolygonShape(); + b2PolygonShape(_shapeSegment).SetAsBox(widthSegment / ws, heightSegment / ws); + } + + override protected function defineFixture():void { + super.defineFixture(); + + _vecFixtureDefBridge = new Vector.(); + var fixtureDefChain:b2FixtureDef; + for (var i:uint = 0; i < numSegments; ++i) { + fixtureDefChain = new b2FixtureDef(); + fixtureDefChain.shape = _shapeSegment; + fixtureDefChain.density = density; + fixtureDefChain.friction = friction; + fixtureDefChain.restitution = restitution; + fixtureDefChain.filter.maskBits = PhysicsCollisionCategories.Get("GoodGuys"); + _vecFixtureDefBridge.push(fixtureDefChain); + } + } + + override protected function createFixture():void { + super.createFixture(); + + var i:uint = 0; + for each (var fixtureDefChain:b2FixtureDef in _vecFixtureDefBridge) { + _vecBodyBridge[i].CreateFixture(fixtureDefChain); + ++i; + } + } + + override protected function defineJoint():void { + _vecRevoluteJointDef = new Vector.(); + + for (var i:uint = 0; i < numSegments; ++i) { + + if (i == 0) + revoluteJoint(leftAnchor.body, _vecBodyBridge[i], new b2Vec2(leftAnchor.width / 2 / ws, (-leftAnchor.height / 2 + heightSegment) / ws), new b2Vec2(-widthSegment / ws, 0)); + else + revoluteJoint(_vecBodyBridge[i - 1], _vecBodyBridge[i], new b2Vec2(widthSegment / ws, 0), new b2Vec2(-widthSegment / ws, 0)); + } + revoluteJoint(_vecBodyBridge[numSegments - 1], rightAnchor.body, new b2Vec2(widthSegment / ws, 0), new b2Vec2(-(rightAnchor.width / 2 / ws), (-rightAnchor.height / 2 + heightSegment) / ws)); + _body.SetActive(false); + } + + private function revoluteJoint(bodyA:b2Body, bodyB:b2Body, anchorA:b2Vec2, anchorB:b2Vec2):void { + + var revoluteJointDef:b2RevoluteJointDef = new b2RevoluteJointDef(); + revoluteJointDef.localAnchorA.Set(anchorA.x, anchorA.y); + revoluteJointDef.localAnchorB.Set(anchorB.x, anchorB.y); + revoluteJointDef.bodyA = bodyA; + revoluteJointDef.bodyB = bodyB; + revoluteJointDef.enableMotor = true; + revoluteJointDef.motorSpeed = 0; + revoluteJointDef.maxMotorTorque = 1.0; + revoluteJointDef.collideConnected = false; + _vecRevoluteJointDef.push(revoluteJointDef); + } + + override protected function createJoint():void { + for each (var revoluteJointDef:b2RevoluteJointDef in _vecRevoluteJointDef) { + _box2D.world.CreateJoint(revoluteJointDef); + } + } + + public function initDisplay():void { + + display = true; + _vecSprites = new Vector.(); + + for (var i:uint = 0; i < numSegments; ++i) { + var img:Image = new Image(segmentTexture); + img.scaleX = img.scaleY = (widthSegment) * 2 / segmentTexture.width; + var image:CitrusSprite = new CitrusSprite(i.toString(), {group:2, width:_width * 2, height:_height * 2, view:img, registration:"center"}); + _ce.state.add(image); + _vecSprites.push(image); + } + } + + public function updateSegmentDisplay():void { + + var i:uint = 0; + for each (var body:b2Body in _vecBodyBridge) { + _vecSprites[i].x = body.GetPosition().x * ws; + _vecSprites[i].y = body.GetPosition().y * ws; + _vecSprites[i].rotation = rad2deg(body.GetAngle()); + ++i; + } + } + } +} diff --git a/src/citrus/objects/complex/box2dstarling/FluidBox.as b/src/citrus/objects/complex/box2dstarling/FluidBox.as new file mode 100644 index 00000000..dc649b11 --- /dev/null +++ b/src/citrus/objects/complex/box2dstarling/FluidBox.as @@ -0,0 +1,210 @@ +package citrus.objects.complex.box2dstarling { + + import Box2D.Collision.Shapes.b2CircleShape; + import Box2D.Collision.Shapes.b2PolygonShape; + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.Joints.b2RevoluteJointDef; + import Box2D.Dynamics.b2Body; + import Box2D.Dynamics.b2BodyDef; + import Box2D.Dynamics.b2FilterData; + import Box2D.Dynamics.b2Fixture; + import Box2D.Dynamics.b2FixtureDef; + + import citrus.objects.Box2DPhysicsObject; + import citrus.objects.CitrusSprite; + import citrus.physics.PhysicsCollisionCategories; + + import starling.display.BlendMode; + import starling.display.Image; + import starling.extensions.filters.ThresholdFilter; + import starling.textures.RenderTexture; + import starling.textures.Texture; + import starling.utils.deg2rad; + import starling.utils.rad2deg; + + import flash.display.BitmapData; + import flash.display.Shape; + import flash.filters.BlurFilter; + import flash.geom.Matrix; + + /** + * Example Object for simulating liquid using thresholdfilter. + * Its a rotating box, just place it anywhere. You need to include ThresholdFilter.as + * #author Thomas Zenkner + */ + public class FluidBox extends Box2DPhysicsObject { + + public var bcWidth:Number = 550; + public var bcHeight:Number = 240; + public var bcThickness:Number = 40; + public var ws:int = 30; + public var numBalls:int = 15; + + private var ball:b2Body; + private var ballFixtureDef:b2FixtureDef; + private var ballFixture:b2Fixture; + private var ballshape:b2CircleShape; + private var ballBodyDef:b2BodyDef; + private var _vecSprites:Vector.; + private var _vecBody:Vector.; + private var texture:Texture; + private var thresholdFilter:ThresholdFilter; + private var renderTexture:RenderTexture; + private var renderSprite:CitrusSprite; + private var m:Matrix; + private var ballRadius:uint = 10; + private var offX:Number; + private var offY:Number; + private var anchor:Box2DPhysicsObject; + private var circleShape:Shape; + private var circleData:BitmapData; + + public function FluidBox(name:String, params:Object = null) { + + updateCallEnabled = true; + + super(name, params); + } + + override public function update(timeDelta:Number):void { + super.update(timeDelta); + + var i:uint = 0; + if (_vecSprites[0]) { + + for each (var ball:b2Body in _vecBody) { + + _vecSprites[i].x = _vecBody[i].GetPosition().x * ws; + _vecSprites[i].y = _vecBody[i].GetPosition().y * ws; + _vecSprites[i].rotation = rad2deg(_vecBody[i].GetAngle()); + ++i; + } + + renderTexture.drawBundled(function():void { + i = 0; + for each (var image:CitrusSprite in _vecSprites) { + m.identity(); + m.translate(image.x - offX, image.y - offY); + renderTexture.draw(image.view, m); + ++i; + } + }); + } + } + + override protected function defineBody():void { + + _bodyDef = new b2BodyDef(); + _bodyDef.type = b2Body.b2_dynamicBody; + _bodyDef.position.Set(x / ws, y / ws); + + ballBodyDef = new b2BodyDef(); + ballBodyDef.type = b2Body.b2_dynamicBody; + } + + override protected function createBody():void { + _body = _box2D.world.CreateBody(_bodyDef); + _body.SetUserData(this); + } + + override protected function createShape():void { + _shape = new b2PolygonShape(); + b2PolygonShape(_shape).SetAsOrientedBox(bcWidth / 2 / ws, bcThickness / 2 / ws, new b2Vec2(), deg2rad(0)); + ballshape = new b2CircleShape(10 / ws); + } + + override protected function defineFixture():void { + _fixtureDef = new b2FixtureDef(); + _fixtureDef.shape = _shape; + _fixtureDef.density = 1; + _fixtureDef.friction = 0; + _fixtureDef.restitution = 0.7; + } + + override protected function createFixture():void { + + _fixture = _body.CreateFixture(_fixtureDef); + ballFixtureDef = new b2FixtureDef(); + ballFixtureDef.density = 1; + ballFixtureDef.friction = 0; + ballFixtureDef.restitution = 0.7; + + _shape = new b2PolygonShape(); + b2PolygonShape(_shape).SetAsOrientedBox(bcWidth / 2 / ws, bcThickness / 2 / ws, new b2Vec2(0, -bcHeight / ws + bcThickness / ws)); + _fixtureDef.shape = _shape; + _body.CreateFixture(_fixtureDef); + b2PolygonShape(_shape).SetAsOrientedBox(bcThickness / 2 / ws, bcHeight / 2 / ws, new b2Vec2((bcWidth / 2 + bcThickness / 2) / ws, -(bcHeight / 2 / ws - bcThickness / 2 / ws))); + _fixtureDef.shape = _shape; + _body.CreateFixture(_fixtureDef); + b2PolygonShape(_shape).SetAsOrientedBox(bcThickness / 2 / ws, bcHeight / 2 / ws, new b2Vec2((-bcWidth / 2 - bcThickness / 2) / ws, -(bcHeight / 2 / ws - bcThickness / 2 / ws))); + _fixtureDef.shape = _shape; + _body.CreateFixture(_fixtureDef); + + var blur:int = 35; + circleShape = new Shape(); + circleShape.graphics.beginFill(0x0099ff, 0.8); + circleShape.graphics.drawCircle(ballRadius * 2, ballRadius * 2, ballRadius * 2); + circleShape.graphics.endFill(); + circleShape.filters = [new BlurFilter(blur, blur)]; + + m = new Matrix(); + m.translate(blur, blur); + circleData = new BitmapData(ballRadius * 4 + 2 * blur, ballRadius * 4 + 2 * blur, true, 0x00000000); + circleData.draw(circleShape, m); + texture = Texture.fromBitmapData(circleData, true, true, 1); + + _vecSprites = new Vector.(); + _vecBody = new Vector.(); + thresholdFilter = new ThresholdFilter(0.7); + renderTexture = new RenderTexture(bcWidth + 2 * bcThickness + 10, bcHeight * 4, false); + + var fi:Image = new Image(renderTexture); + fi.filter = thresholdFilter; + fi.blendMode = BlendMode.ADD; + renderSprite = new CitrusSprite("render", {view:fi, x:x, y:y - bcHeight / 2 + bcThickness / 2, group:3, width:100, height:100, registration:"center"}); + _ce.state.add(renderSprite); + + anchor = new Box2DPhysicsObject("anchor", {x:renderSprite.x, y:renderSprite.y}); + anchor.body.SetType(b2Body.b2_staticBody); + var filter:b2FilterData = new b2FilterData(); + filter.maskBits = PhysicsCollisionCategories.GetNone(); + anchor.body.GetFixtureList().SetFilterData(filter); + _ce.state.add(anchor); + + offX = renderSprite.x - renderTexture.width / 2 + texture.width / 2; + offY = renderSprite.y - renderTexture.height / 2 + texture.height / 2; + m = new Matrix(); + + for (var i:int = 0; i < 4; i++) { + for (var j:int = 0; j < numBalls; j++) { + ballBodyDef.position.Set(x / ws - bcWidth / 2 / ws + 25 / ws + j * 30 / ws, y / ws - 30 / ws - 30 / ws * i); + ball = _box2D.world.CreateBody(ballBodyDef); + ball.SetUserData(this); + ballshape = new b2CircleShape(ballRadius / ws); + ballFixtureDef.shape = ballshape; + ball.CreateFixture(ballFixtureDef); + var image:CitrusSprite = new CitrusSprite(i.toString(), {x:ball.GetPosition().x * ws, y:ball.GetPosition().y * ws, group:2, width:_width * 2, height:_height * 2, view:new Image(texture), registration:"center"}); + _vecSprites.push(image); + _vecBody.push(ball); + } + } + } + + override protected function defineJoint():void { + revoluteJoint(anchor.body, _body, new b2Vec2(0, 0), new b2Vec2(0, -bcHeight / 2 / ws + bcThickness / 2 / ws)); + } + + private function revoluteJoint(bodyA:b2Body, bodyB:b2Body, anchorA:b2Vec2, anchorB:b2Vec2):void { + var revoluteJointDef:b2RevoluteJointDef = new b2RevoluteJointDef(); + revoluteJointDef.localAnchorA.Set(anchorA.x, anchorA.y); + revoluteJointDef.localAnchorB.Set(anchorB.x, anchorB.y); + revoluteJointDef.bodyA = bodyA; + revoluteJointDef.bodyB = bodyB; + revoluteJointDef.motorSpeed = Math.PI / 16; + revoluteJointDef.enableMotor = true; + revoluteJointDef.maxMotorTorque = 10000; + revoluteJointDef.collideConnected = false; + _box2D.world.CreateJoint(revoluteJointDef); + } + } +} \ No newline at end of file diff --git a/src/citrus/objects/complex/box2dstarling/Pool.as b/src/citrus/objects/complex/box2dstarling/Pool.as new file mode 100644 index 00000000..3793beb9 --- /dev/null +++ b/src/citrus/objects/complex/box2dstarling/Pool.as @@ -0,0 +1,175 @@ +package citrus.objects.complex.box2dstarling { + + import Box2D.Collision.Shapes.b2PolygonShape; + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.Controllers.b2BuoyancyController; + import Box2D.Dynamics.b2Body; + import Box2D.Dynamics.b2BodyDef; + import Box2D.Dynamics.b2Fixture; + import Box2D.Dynamics.b2FixtureDef; + + import citrus.objects.Box2DPhysicsObject; + import citrus.physics.box2d.Box2DUtils; + + + /** + * Pool uses the BuoyancyController to simulate liquid physics. It's a rectangular region in which the controller influences the bodies inside of it + */ + + public class Pool extends Box2DPhysicsObject + { + /** + *strength of the surrounding bodies + */ + [Inspectable(defaultValue="5")] + public var wallThickness:Number = 5; + + /** + *build the left wall? + */ + [Inspectable(defaultValue="true")] + public var leftWall:Boolean = true; + + /** + *build the right wall? + */ + [Inspectable(defaultValue="true")] + public var rightWall:Boolean = true; + + /** + *build the bottom? + */ + [Inspectable(defaultValue="true")] + public var bottom:Boolean = true; + + // These are the parameters for the water area + [Inspectable(defaultValue="2")] + public var density:Number = 2; + + [Inspectable(defaultValue="3")] + public var linearDrag:Number=3; + + [Inspectable(defaultValue="2")] + public var angularDrag:Number=2; + + + private var ws:int = 30;//worldscale + private var pool:b2Body; + private var poolFixtureDef:b2FixtureDef; + private var poolFixture:b2Fixture; + + private var buoyancyController:b2BuoyancyController = new b2BuoyancyController(); + + public function Pool(name:String, params:Object=null) + { + + _beginContactCallEnabled = true; + _endContactCallEnabled = true; + + super(name, params); + } + + override public function destroy():void + { + _box2D.world.DestroyController(buoyancyController); + _box2D.world.DestroyBody(pool); + super.destroy(); + } + + override protected function defineBody():void + { + _bodyDef = new b2BodyDef(); + _bodyDef.type = b2Body.b2_staticBody; + _bodyDef.position.Set(x/ws, y/ws); + } + + override protected function createBody():void + { + //the water body + _body = _box2D.world.CreateBody(_bodyDef); + _body.SetUserData(this); + //surrounding body + pool = _box2D.world.CreateBody(_bodyDef); + pool.SetUserData(this); + } + + override protected function createShape():void + { + _shape = new b2PolygonShape(); + b2PolygonShape(_shape).SetAsOrientedBox(_width/2, _height/2 - wallThickness/ws, new b2Vec2(0, -wallThickness/ws)); + } + + override protected function defineFixture():void + { + _fixtureDef = new b2FixtureDef(); + _fixtureDef.shape = _shape; + _fixtureDef.isSensor = true; + _fixtureDef.userData = {name:"water"}; + } + + override protected function createFixture():void + { + _fixture = _body.CreateFixture(_fixtureDef); + + poolFixtureDef = new b2FixtureDef(); + poolFixtureDef.isSensor = false; + poolFixtureDef.friction = 0.6; + poolFixtureDef.restitution = 0.3; + poolFixtureDef.userData = {name:"pool"}; + + if (leftWall) + { + _shape = new b2PolygonShape(); + b2PolygonShape(_shape).SetAsOrientedBox(wallThickness/ws, _height/2, new b2Vec2(-_width/2 - wallThickness/ws, 0)); + poolFixtureDef.shape = _shape; + pool.CreateFixture(poolFixtureDef); + } + + if(rightWall) + { + _shape = new b2PolygonShape(); + b2PolygonShape(_shape).SetAsOrientedBox(wallThickness/ws, _height/2, new b2Vec2(_width/2 + wallThickness/ws, 0)); + poolFixtureDef.shape = _shape; + pool.CreateFixture(poolFixtureDef); + } + + if (bottom) + { + _shape = new b2PolygonShape(); + b2PolygonShape(_shape).SetAsOrientedBox(_width/2, wallThickness/ws, new b2Vec2(0, _height/2 - wallThickness/ws)); + poolFixtureDef.shape = _shape; + pool.CreateFixture(poolFixtureDef); + } + + buoyancyController.normal.Set(0,-1); + buoyancyController.offset = -(_y - _height/2); + buoyancyController.useDensity = true; + buoyancyController.density = density; + buoyancyController.linearDrag = linearDrag; + buoyancyController.angularDrag = angularDrag; + _box2D.world.AddController(buoyancyController); + } + + override public function handleBeginContact(contact:b2Contact):void + { + if(contact.GetFixtureA() == _fixture || contact.GetFixtureB() == _fixture) + { + //needs better checking if multiple controllers are used + if (Box2DUtils.CollisionGetOther(this, contact).body.GetControllerList() == null) { + buoyancyController.AddBody(Box2DUtils.CollisionGetOther(this, contact).body); + } + } + } + + override public function handleEndContact(contact:b2Contact):void + { + if(contact.GetFixtureA() == _fixture || contact.GetFixtureB() == _fixture) + { + if (Box2DUtils.CollisionGetOther(this, contact).body.GetControllerList() != null) { + buoyancyController.RemoveBody(Box2DUtils.CollisionGetOther(this, contact).body); + } + } + } + } +} diff --git a/src/citrus/objects/complex/box2dstarling/Rope.as b/src/citrus/objects/complex/box2dstarling/Rope.as new file mode 100644 index 00000000..c2b3c449 --- /dev/null +++ b/src/citrus/objects/complex/box2dstarling/Rope.as @@ -0,0 +1,324 @@ +package citrus.objects.complex.box2dstarling { + + import Box2D.Collision.Shapes.b2PolygonShape; + import Box2D.Collision.Shapes.b2Shape; + import Box2D.Collision.b2Manifold; + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.Joints.b2Joint; + import Box2D.Dynamics.Joints.b2RevoluteJointDef; + import Box2D.Dynamics.b2Body; + import Box2D.Dynamics.b2BodyDef; + import Box2D.Dynamics.b2FixtureDef; + + import citrus.objects.Box2DPhysicsObject; + import citrus.objects.CitrusSprite; + import citrus.objects.platformer.box2d.Hero; + import citrus.physics.box2d.Box2DUtils; + + import starling.display.Image; + import starling.textures.Texture; + import starling.utils.deg2rad; + import starling.utils.rad2deg; + + import org.osflash.signals.Signal; + + import flash.events.TimerEvent; + import flash.utils.Timer; + import flash.utils.setTimeout; + + /** + * A hanging rope where you can hang on and swing... + * If you use the included functions for moving on the rope, you should declare a string variable + * (or property on your hero) and assign this.name when onHang is dispatched to get reference to the + * attached rope + * e.g (_ce.state.getObjectByName(ropeName) as Rope).stopClimbing(); + */ + public class Rope extends Box2DPhysicsObject { + + public var onHang:Signal; + public var onHangEnd:Signal; + + /** + * The object where the rope is attached(centered) + */ + public var anchor:Box2DPhysicsObject; + public var ropeLength:uint = 200; + public var numSegments:uint = 9; + public var widthSegment:uint = 15; + public var useTexture:Boolean = false; + /** + * Texture for the segments + */ + public var segmentTexture:Texture; + /** + * The position where the hero is connected, relative to his origin + */ + public var heroAnchorOffset:b2Vec2; + /** + * The Impulse applied to the hero's center of mass when jump off the rope + */ + public var leaveImpulse:b2Vec2; + public var maxSwingVelocity:Number; + + private var hero:Hero; + private var ws:Number = 30;//worldscale + private var heightSegment:uint; + private var maxV:Number; + + private var _vecBodyDefRope:Vector.; + private var _vecBodyRope:Vector.; + private var _vecFixtureDefRope:Vector.; + private var _vecRevoluteJointDef:Vector.; + private var _vecSprites:Vector.; + private var _shapeRope:b2Shape; + + private var connectingJoint:b2Joint; + private var targetJointIndex:int; + + private var displayReady:Boolean = false; + private var ropeAdded:Boolean = false; + private var up:Boolean; + private var moveTimer:Timer; + + public function Rope(name:String, params:Object = null) { + + updateCallEnabled = true; + _preContactCallEnabled = true; + + super(name, params); + + onHang = new Signal(); + onHangEnd = new Signal(); + + moveTimer = new Timer(50, 0); + moveTimer.addEventListener(TimerEvent.TIMER, onMoveTimer); + } + + override public function destroy():void + { + onHang.removeAll(); + onHangEnd.removeAll(); + + var i:uint = 0; + for each (var bodyRope:b2Body in _vecBodyRope) { + _box2D.world.DestroyBody(bodyRope); + _ce.state.remove(_vecSprites[i]); + ++i; + } + super.destroy(); + } + + override public function update(timeDelta:Number):void { + super.update(timeDelta); + if (displayReady) + updateSegmentDisplay(); + } + + override protected function defineBody():void { + super.defineBody(); + + heightSegment = ropeLength/numSegments + if (useTexture) + { + initDisplay(); + } + _vecBodyDefRope = new Vector.(); + var bodyDefRope:b2BodyDef; + for (var i:uint = 0; i < numSegments; ++i) + { + bodyDefRope = new b2BodyDef(); + bodyDefRope.type = b2Body.b2_dynamicBody; + bodyDefRope.position.Set(anchor.x/ws, anchor.y/ws + anchor.height/2/ws + i*heightSegment/ws); + _vecBodyDefRope.push(bodyDefRope); + } + } + + override protected function createBody():void { + super.createBody(); + _vecBodyRope = new Vector.(); + var bodyRope:b2Body; + for each (var bodyDefRope:b2BodyDef in _vecBodyDefRope) + { + bodyRope = _box2D.world.CreateBody(bodyDefRope); + bodyRope.SetUserData(this); + _vecBodyRope.push(bodyRope); + } + } + + override protected function createShape():void { + super.createShape(); + _shapeRope = new b2PolygonShape(); + b2PolygonShape(_shapeRope).SetAsBox(widthSegment/ws, heightSegment/ws); + } + + override protected function defineFixture():void { + super.defineFixture(); + _vecFixtureDefRope = new Vector.(); + var fixtureDefRope:b2FixtureDef; + for (var i:uint = 0; i < numSegments; ++i) + { + fixtureDefRope = new b2FixtureDef(); + fixtureDefRope.shape = _shapeRope; + fixtureDefRope.density = 35; + fixtureDefRope.friction = 1; + fixtureDefRope.restitution = 0; + fixtureDefRope.userData = {name:i}; + _vecFixtureDefRope.push(fixtureDefRope); + } + } + + override protected function createFixture():void { + super.createFixture(); + var i:uint = 0; + for each (var fixtureDefRope:b2FixtureDef in _vecFixtureDefRope) { + _vecBodyRope[i].CreateFixture(fixtureDefRope); + ++i; + } + } + + override protected function defineJoint():void { + _vecRevoluteJointDef = new Vector.(); + for (var i:uint = 0; i < numSegments; ++i) { + + if (i == 0) + revoluteJoint(anchor.body, _vecBodyRope[i] ,new b2Vec2(0, anchor.height/2/ws), new b2Vec2(0, -heightSegment/ws)); + else + revoluteJoint(_vecBodyRope[i - 1], _vecBodyRope[i],new b2Vec2(0, (heightSegment-2)/ws), new b2Vec2(0, -heightSegment/ws)); + } + } + + private function revoluteJoint(bodyA:b2Body,bodyB:b2Body,anchorA:b2Vec2,anchorB:b2Vec2):void { + var revoluteJointDef:b2RevoluteJointDef=new b2RevoluteJointDef(); + revoluteJointDef.localAnchorA.Set(anchorA.x,anchorA.y); + revoluteJointDef.localAnchorB.Set(anchorB.x,anchorB.y); + revoluteJointDef.bodyA=bodyA; + revoluteJointDef.bodyB=bodyB; + revoluteJointDef.motorSpeed = 0; + revoluteJointDef.enableMotor = true; + revoluteJointDef.maxMotorTorque = 0.1; + revoluteJointDef.collideConnected = false; + _vecRevoluteJointDef.push(revoluteJointDef); + } + + override protected function createJoint():void { + for each (var revoluteJointDef:b2RevoluteJointDef in _vecRevoluteJointDef) { + _box2D.world.CreateJoint(revoluteJointDef); + } + if (heroAnchorOffset == null) heroAnchorOffset = new b2Vec2(); + else heroAnchorOffset.Multiply(1/30); + if (leaveImpulse == null) leaveImpulse = new b2Vec2(0, -100); + _body.SetActive(false); + hero = _ce.state.getFirstObjectByType(Hero) as Hero; + maxV = hero.maxVelocity; + } + + override public function handlePreSolve(contact:b2Contact, oldManifold:b2Manifold):void { + contact.SetEnabled(false); + if (Box2DUtils.CollisionGetOther(this, contact) is Hero){ + if (!ropeAdded && !hero.body.GetJointList()) + { + targetJointIndex = int(((hero.getBody().GetPosition().y*ws - (hero.height)/2) - _vecBodyRope[0].GetPosition().y*ws)/(heightSegment*2-2)); + if (targetJointIndex < 1) targetJointIndex = 1; + else if (targetJointIndex > _vecBodyRope.length-1) targetJointIndex = _vecBodyRope.length-1; + + revoluteJoint(_vecBodyRope[targetJointIndex], hero.body, new b2Vec2(0, heightSegment/ws), heroAnchorOffset); + connectingJoint = _box2D.world.CreateJoint(_vecRevoluteJointDef[_vecRevoluteJointDef.length-1]); + ropeAdded = true; + hero.body.SetFixedRotation(false); + hero.maxVelocity = maxSwingVelocity; + onHang.dispatch(); + // if you don't want to us signals put the necessary assignments here + // e.g hero.isHanging = true; (hero as yourHeroClass).currentRope = this.name; + } + } + } + + //when startClimbing() is called, a timer starts and onTick the hero travels up or down + //till he reaches end of the rope or stopClimbing() is called + //other solutions are welcome ;) + protected function onMoveTimer(event:TimerEvent=null):void + { + if (up && targetJointIndex >= 1) + { + moveTimer.delay = 150; + _box2D.world.DestroyJoint(connectingJoint); + _vecRevoluteJointDef[_vecRevoluteJointDef.length-1] = null; + revoluteJoint(_vecBodyRope[targetJointIndex-1], hero.body, new b2Vec2(0, heightSegment/ws), heroAnchorOffset); + connectingJoint = _box2D.world.CreateJoint(_vecRevoluteJointDef[_vecRevoluteJointDef.length-1]); + targetJointIndex--; + } + else if (up && targetJointIndex == 0) { + _box2D.world.DestroyJoint(connectingJoint); + _vecRevoluteJointDef[_vecRevoluteJointDef.length-1] = null; + revoluteJoint(anchor.body, hero.body,new b2Vec2(0, anchor.height/2/ws), heroAnchorOffset); + connectingJoint = _box2D.world.CreateJoint(_vecRevoluteJointDef[_vecRevoluteJointDef.length-1]); + } + else if (!up && targetJointIndex < _vecBodyRope.length-1) + { + moveTimer.delay = 50; + _box2D.world.DestroyJoint(connectingJoint); + _vecRevoluteJointDef[_vecRevoluteJointDef.length-1] = null; + revoluteJoint(_vecBodyRope[targetJointIndex+1], hero.body, new b2Vec2(0, heightSegment/ws), heroAnchorOffset); + connectingJoint = _box2D.world.CreateJoint(_vecRevoluteJointDef[_vecRevoluteJointDef.length-1]); + targetJointIndex++; + } + } + + /** + * pass in the direction true:up, false:down + */ + public function startClimbing(upwards:Boolean):void + { + up = upwards; + onMoveTimer(); + moveTimer.start(); + } + + public function stopClimbing():void + { + moveTimer.reset(); + } + + public function removeJoint():void + { + _box2D.world.DestroyJoint(connectingJoint); + _vecRevoluteJointDef[_vecRevoluteJointDef.length-1] = null; + connectingJoint = null; + + /// TO MANAGE IN YOUR HERO CLASS /// + // (hero as HeroSnowman).isHanging = false; + + onHangEnd.dispatch(); + hero.body.ApplyImpulse(leaveImpulse, hero.body.GetWorldCenter()); + hero.body.SetAngle(deg2rad(0)); + hero.body.SetAngularVelocity(0); + hero.body.SetFixedRotation(true); + hero.maxVelocity = maxV; + setTimeout(function():void{ropeAdded = false;}, 1000); + } + + private function initDisplay():void { + displayReady = true; + _vecSprites = new Vector.(); + + for (var i:uint = 0; i < numSegments; ++i) { + var img:Image = new Image(segmentTexture); + img.scaleX = img.scaleY = (heightSegment) * 2 / segmentTexture.width; + var image:CitrusSprite = new CitrusSprite(i.toString(), {group:2, width:heightSegment * 2, height:widthSegment * 2, view:img, registration:"center"}); + _ce.state.add(image); + _vecSprites.push(image); + } + } + + private function updateSegmentDisplay():void { + var i:uint = 0; + for each (var bodyRope:b2Body in _vecBodyRope) { + _vecSprites[i].x = bodyRope.GetPosition().x * ws; + _vecSprites[i].y = bodyRope.GetPosition().y * ws; + _vecSprites[i].rotation = rad2deg(bodyRope.GetAngle())+90; + ++i; + } + } + } +} diff --git a/src/citrus/objects/platformer/box2d/Cannon.as b/src/citrus/objects/platformer/box2d/Cannon.as new file mode 100644 index 00000000..45fc974b --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Cannon.as @@ -0,0 +1 @@ +package citrus.objects.platformer.box2d { import citrus.objects.Box2DPhysicsObject; import org.osflash.signals.Signal; import flash.display.MovieClip; import flash.events.TimerEvent; import flash.utils.Timer; /** * A cannon is an object which fires missiles. A cannon is a static body so it extends Platform. *
      Properties: *
    • fireRate : The frequency that missiles are fired.
    • *
    • startingDirection : The direction that missiles are fired.
    • *
    • openFire : Indicate if the cannon shoot at start or not.
    * *
      Events: *
    • onGiveDamage - Dispatched when the missile explodes on a Box2DPhysicsObject. Passes one parameter: * The Object it exploded on (Box2DPhysicsObject)
    */ public class Cannon extends Platform { /** * The frequency that missiles are fired. */ [Inspectable(defaultValue="2000")] public var fireRate:Number = 2000; /** * The direction that missiles are fired */ [Inspectable(defaultValue="right",enumeration="right,left")] public var startingDirection:String = "right"; /** * Indicate if the cannon shoot at start or not. */ [Inspectable(defaultValue="true")] public var openFire:Boolean = true; [Inspectable(defaultValue="20")] public var missileWidth:uint = 20; [Inspectable(defaultValue="20")] public var missileHeight:uint = 20; [Inspectable(defaultValue="2")] public var missileSpeed:Number = 2; [Inspectable(defaultValue="0")] public var missileAngle:Number = 0; [Inspectable(defaultValue="1000")] public var missileExplodeDuration:Number = 1000; [Inspectable(defaultValue="10000")] public var missileFuseDuration:Number = 10000; [Inspectable(defaultValue="",format="File",type="String")] public var missileView:* = MovieClip; /** * Dispatched when the missile explodes on a Box2DPhysicsObject. Passes one parameter: * The Object it exploded on (Box2DPhysicsObject) */ public var onGiveDamage:Signal; protected var _firing:Boolean = false; protected var _timer:Timer; public function Cannon(name:String, params:Object = null) { super(name, params); onGiveDamage = new Signal(Box2DPhysicsObject); } override public function initialize(poolObjectParams:Object = null):void { super.initialize(poolObjectParams); if (openFire) startFire(); } override public function destroy():void { onGiveDamage.removeAll(); _ce.onPlayingChange.remove(_playingChanged); _timer.stop(); _timer.removeEventListener(TimerEvent.TIMER, _fire); super.destroy(); } /** * Dispatch onGiveDamage's Signal if a missile fired by the cannon explodes. */ protected function _damage(missile:Missile, contact:Box2DPhysicsObject):void { if (contact != null) onGiveDamage.dispatch(contact); } /** * Cannon starts to fire. The timer is also started with the fireRate's frequency. */ public function startFire():void { _firing = true; _updateAnimation(); _timer = new Timer(fireRate); _timer.addEventListener(TimerEvent.TIMER, _fire); _timer.start(); _ce.onPlayingChange.add(_playingChanged); } /** * Cannon stops to fire, timer is stopped. */ public function stopFire():void { _firing = false; _updateAnimation(); _timer.stop(); _timer.removeEventListener(TimerEvent.TIMER, _fire); _ce.onPlayingChange.remove(_playingChanged); } /** * Cannon fires a missile. This missile called the _damage function on missile's explosion. */ protected function _fire(tEvt:TimerEvent):void { var missile:Missile; if (startingDirection == "right") missile = new Missile("Missile", {x:x + width, y:y, width:missileWidth, height:missileHeight, speed:missileSpeed, angle:missileAngle, explodeDuration:missileExplodeDuration, fuseDuration:missileFuseDuration, view:missileView}); else missile = new Missile("Missile", {x:x - width, y:y, width:missileWidth, height:missileHeight, speed:-missileSpeed, angle:missileAngle, explodeDuration:missileExplodeDuration, fuseDuration:missileFuseDuration, view:missileView}); _ce.state.add(missile); missile.onExplode.addOnce(_damage); } protected function _updateAnimation():void { _animation = _firing ? "fire" : "normal"; } /** * Start or stop the timer. Automatically called by the engine when the game is paused/unpaused. */ protected function _playingChanged(playing:Boolean):void { playing ? _timer.start() : _timer.stop(); } } } \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/Coin.as b/src/citrus/objects/platformer/box2d/Coin.as new file mode 100644 index 00000000..a1ab20e9 --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Coin.as @@ -0,0 +1,55 @@ +package citrus.objects.platformer.box2d { + + import Box2D.Dynamics.Contacts.b2Contact; + + import citrus.physics.box2d.Box2DShapeMaker; + import citrus.physics.box2d.Box2DUtils; + import citrus.physics.box2d.IBox2DPhysicsObject; + + import flash.utils.getDefinitionByName; + + /** + * Coin is basically a sensor that destroys itself when a particular class type touches it (often a Hero). + */ + public class Coin extends Sensor { + + protected var _collectorClass:Class = Hero; + + public function Coin(name:String, params:Object = null) { + super(name, params); + } + + /** + * The Coin uses the collectorClass parameter to know who can collect it. + * Use this setter to pass in which base class the collector should be, in String form + * or Object notation. + * For example, if you want to set the "Hero" class as your hero's enemy, pass + * "citrus.objects.platformer.box2d.Hero" or Hero directly (no quotes). Only String + * form will work when creating objects via a level editor. + */ + [Inspectable(defaultValue="citrus.objects.platformer.box2d.Hero")] + public function set collectorClass(value:*):void { + if (value is String) + _collectorClass = getDefinitionByName(value as String) as Class; + else if (value is Class) + _collectorClass = value; + } + + override protected function createShape():void { + _shape = Box2DShapeMaker.Circle(_width, _height); + } + + /** + * On contact, if the collider is the collector the coin is removed. The contact is also dispatched. + */ + override public function handleBeginContact(contact:b2Contact):void { + + super.handleBeginContact(contact); + + var collider:IBox2DPhysicsObject = Box2DUtils.CollisionGetOther(this, contact); + + if (_collectorClass && collider is _collectorClass) + kill = true; + } + } +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/Crate.as b/src/citrus/objects/platformer/box2d/Crate.as new file mode 100644 index 00000000..153bd1d6 --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Crate.as @@ -0,0 +1,29 @@ +package citrus.objects.platformer.box2d +{ + + import citrus.objects.Box2DPhysicsObject; + + /** + * An object made for Continuous Collision Detection. It should only be used for very fast, small moving dynamic bodies. + */ + public class Crate extends Box2DPhysicsObject + { + public function Crate(name:String, params:Object=null) + { + super(name, params); + } + + override protected function defineBody():void + { + super.defineBody(); + _bodyDef.bullet = true; + } + + override protected function defineFixture():void + { + super.defineFixture(); + _fixtureDef.density = 0.1; + _fixtureDef.restitution = 0; + } + } +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/Enemy.as b/src/citrus/objects/platformer/box2d/Enemy.as new file mode 100644 index 00000000..e5864db7 --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Enemy.as @@ -0,0 +1,222 @@ +package citrus.objects.platformer.box2d { + + import Box2D.Collision.Shapes.b2PolygonShape; + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.b2Fixture; + import Box2D.Dynamics.b2FixtureDef; + + import citrus.math.MathVector; + import citrus.objects.Box2DPhysicsObject; + import citrus.physics.PhysicsCollisionCategories; + import citrus.physics.box2d.Box2DShapeMaker; + import citrus.physics.box2d.Box2DUtils; + import citrus.physics.box2d.IBox2DPhysicsObject; + + import flash.geom.Point; + import flash.utils.clearTimeout; + import flash.utils.getDefinitionByName; + import flash.utils.setTimeout; + + /** + * This is a common example of a side-scrolling bad guy. He has limited logic, basically + * only turning around when he hits a wall. + * + * When controlling collision interactions between two objects, such as a Hero and Enemy, + * I like to let each object perform its own actions, not control one object's action from the other object. + * For example, the Hero doesn't contain the logic for killing the Enemy, and the Enemy doesn't contain the + * logic for making the hero "Spring" when he kills him. + */ + public class Enemy extends Box2DPhysicsObject + { + [Inspectable(defaultValue="1.3")] + public var speed:Number = 1.3; + + [Inspectable(defaultValue="3")] + public var enemyKillVelocity:Number = 3; + + [Inspectable(defaultValue="left",enumeration="left,right")] + public var startingDirection:String = "left"; + + [Inspectable(defaultValue="400")] + public var hurtDuration:Number = 400; + + [Inspectable(defaultValue="-100000")] + public var leftBound:Number = -100000; + + [Inspectable(defaultValue="100000")] + public var rightBound:Number = 100000; + + [Inspectable(defaultValue="10")] + public var wallSensorOffset:Number = 10; + + [Inspectable(defaultValue="2")] + public var wallSensorWidth:Number = 2; + + [Inspectable(defaultValue="2")] + public var wallSensorHeight:Number = 2; + + protected var _hurtTimeoutID:uint = 0; + protected var _hurt:Boolean = false; + protected var _enemyClass:* = Hero; + + protected var _leftSensorShape:b2PolygonShape; + protected var _rightSensorShape:b2PolygonShape; + protected var _leftSensorFixture:b2Fixture; + protected var _rightSensorFixture:b2Fixture; + protected var _sensorFixtureDef:b2FixtureDef; + + public function Enemy(name:String, params:Object=null) + { + updateCallEnabled = true; + _beginContactCallEnabled = true; + + super(name, params); + + if (startingDirection == "left") + _inverted = true; + } + + override public function destroy():void + { + clearTimeout(_hurtTimeoutID); + + super.destroy(); + } + + public function get enemyClass():* + { + return _enemyClass; + } + + [Inspectable(defaultValue="citrus.objects.platformer.box2d.Hero",type="String")] + public function set enemyClass(value:*):void + { + if (value is String) + _enemyClass = getDefinitionByName(value) as Class; + else if (value is Class) + _enemyClass = value; + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + var position:b2Vec2 = _body.GetPosition(); + + //Turn around when they pass their left/right bounds + if ((_inverted && position.x * _box2D.scale < leftBound) || (!_inverted && position.x * _box2D.scale > rightBound)) + turnAround(); + + var velocity:b2Vec2 = _body.GetLinearVelocity(); + + if (!_hurt) + velocity.x = _inverted ? -speed : speed; + else + velocity.x = 0; + + updateAnimation(); + } + + /** + * The enemy is hurt, start the time out with hurtDuration value. Then it called endHurtState's function. + */ + public function hurt():void + { + _hurt = true; + _hurtTimeoutID = setTimeout(endHurtState, hurtDuration); + } + + /** + * Change enemy's direction + */ + public function turnAround():void + { + _inverted = !_inverted; + } + + override protected function createBody():void + { + super.createBody(); + _body.SetFixedRotation(true); + } + + override protected function createShape():void + { + _shape = Box2DShapeMaker.BeveledRect(_width, _height, 0.2); + + var sensorWidth:Number = wallSensorWidth / _box2D.scale; + var sensorHeight:Number = wallSensorHeight / _box2D.scale; + var sensorOffset:b2Vec2 = new b2Vec2( -_width / 2 - (sensorWidth / 2), _height / 2 - (wallSensorOffset / _box2D.scale)); + + _leftSensorShape = new b2PolygonShape(); + _leftSensorShape.SetAsOrientedBox(sensorWidth, sensorHeight, sensorOffset); + + sensorOffset.x = -sensorOffset.x; + _rightSensorShape = new b2PolygonShape(); + _rightSensorShape.SetAsOrientedBox(sensorWidth, sensorHeight, sensorOffset); + } + + override protected function defineFixture():void + { + super.defineFixture(); + _fixtureDef.friction = 0; + _fixtureDef.filter.categoryBits = PhysicsCollisionCategories.Get("BadGuys"); + _fixtureDef.filter.maskBits = PhysicsCollisionCategories.GetAllExcept("Items"); + + _sensorFixtureDef = new b2FixtureDef(); + _sensorFixtureDef.shape = _leftSensorShape; + _sensorFixtureDef.isSensor = true; + _sensorFixtureDef.filter.categoryBits = PhysicsCollisionCategories.Get("BadGuys"); + _sensorFixtureDef.filter.maskBits = PhysicsCollisionCategories.GetAllExcept("Items"); + } + + override protected function createFixture():void + { + super.createFixture(); + + _leftSensorFixture = body.CreateFixture(_sensorFixtureDef); + + _sensorFixtureDef.shape = _rightSensorShape; + _rightSensorFixture = body.CreateFixture(_sensorFixtureDef); + } + + override public function handleBeginContact(contact:b2Contact):void { + + var collider:IBox2DPhysicsObject = Box2DUtils.CollisionGetOther(this, contact); + + if (collider is _enemyClass && collider.body.GetLinearVelocity().y > enemyKillVelocity) + hurt(); + + if (_body.GetLinearVelocity().x < 0 && (contact.GetFixtureA() == _rightSensorFixture || contact.GetFixtureB() == _rightSensorFixture)) + return; + + if (_body.GetLinearVelocity().x > 0 && (contact.GetFixtureA() == _leftSensorFixture || contact.GetFixtureB() == _leftSensorFixture)) + return; + + if (contact.GetManifold().m_localPoint) { + + var normalPoint:Point = new Point(contact.GetManifold().m_localPoint.x, contact.GetManifold().m_localPoint.y); + var collisionAngle:Number = new MathVector(normalPoint.x, normalPoint.y).angle * 180 / Math.PI; + + if ((collider is Platform && collisionAngle != 90) || collider is Enemy) + turnAround(); + } + + } + + protected function updateAnimation():void + { + _animation = _hurt ? "die" : "walk"; + } + + /** + * The enemy is no more hurt, but it is killed. Override this function to prevent enemy's death. + */ + protected function endHurtState():void + { + _hurt = false; + kill = true; + } + } +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/Hero.as b/src/citrus/objects/platformer/box2d/Hero.as new file mode 100644 index 00000000..3ea12712 --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Hero.as @@ -0,0 +1,490 @@ +package citrus.objects.platformer.box2d +{ + + import Box2D.Collision.b2Manifold; + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.b2Fixture; + import Box2D.Dynamics.Contacts.b2Contact; + import citrus.objects.Box2DPhysicsObject; + import citrus.physics.box2d.Box2DShapeMaker; + import citrus.physics.box2d.Box2DUtils; + import citrus.physics.box2d.IBox2DPhysicsObject; + import citrus.physics.PhysicsCollisionCategories; + import flash.utils.clearTimeout; + import flash.utils.getDefinitionByName; + import flash.utils.setTimeout; + import org.osflash.signals.Signal; + + + + + /** + * This is a common, simple, yet solid implementation of a side-scrolling Hero. + * The hero can run, jump, get hurt, and kill enemies. It dispatches signals + * when significant events happen. The game state's logic should listen for those signals + * to perform game state updates (such as increment coin collections). + * + * Don't store data on the hero object that you will need between two or more levels (such + * as current coin count). The hero should be re-created each time a state is created or reset. + */ + public class Hero extends Box2DPhysicsObject + { + //properties + /** + * This is the rate at which the hero speeds up when you move him left and right. + */ + [Inspectable(defaultValue="1")] + public var acceleration:Number = 1; + + /** + * This is the fastest speed that the hero can move left or right. + */ + [Inspectable(defaultValue="8")] + public var maxVelocity:Number = 8; + + /** + * This is the initial velocity that the hero will move at when he jumps. + */ + [Inspectable(defaultValue="11")] + public var jumpHeight:Number = 11; + + /** + * This is the amount of "float" that the hero has when the player holds the jump button while jumping. + */ + [Inspectable(defaultValue="0.3")] + public var jumpAcceleration:Number = 0.3; + + /** + * This is the y velocity that the hero must be travelling in order to kill an Enemy. + */ + [Inspectable(defaultValue="3")] + public var killVelocity:Number = 3; + + /** + * The y velocity that the hero will spring when he kills an enemy. + */ + [Inspectable(defaultValue="8")] + public var enemySpringHeight:Number = 8; + + /** + * The y velocity that the hero will spring when he kills an enemy while pressing the jump button. + */ + [Inspectable(defaultValue="9")] + public var enemySpringJumpHeight:Number = 9; + + /** + * How long the hero is in hurt mode for. + */ + [Inspectable(defaultValue="1000")] + public var hurtDuration:Number = 1000; + + /** + * The amount of kick-back that the hero jumps when he gets hurt. + */ + [Inspectable(defaultValue="6")] + public var hurtVelocityX:Number = 6; + + /** + * The amount of kick-back that the hero jumps when he gets hurt. + */ + [Inspectable(defaultValue="10")] + public var hurtVelocityY:Number = 10; + + /** + * Determines whether or not the hero's ducking ability is enabled. + */ + [Inspectable(defaultValue="true")] + public var canDuck:Boolean = true; + + /** + * Defines which input Channel to listen to. + */ + [Inspectable(defaultValue = "0")] + public var inputChannel:uint = 0; + + //events + /** + * Dispatched whenever the hero jumps. + */ + public var onJump:Signal; + + /** + * Dispatched whenever the hero gives damage to an enemy. + */ + public var onGiveDamage:Signal; + + /** + * Dispatched whenever the hero takes damage from an enemy. + */ + public var onTakeDamage:Signal; + + /** + * Dispatched whenever the hero's animation changes. + */ + public var onAnimationChange:Signal; + + protected var _groundContacts:Array = [];//Used to determine if he's on ground or not. + protected var _enemyClass:Class = Enemy; + protected var _onGround:Boolean = false; + protected var _springOffEnemy:Number = -1; + protected var _hurtTimeoutID:uint; + protected var _hurt:Boolean = false; + protected var _friction:Number = 0.75; + protected var _playerMovingHero:Boolean = false; + protected var _controlsEnabled:Boolean = true; + protected var _ducking:Boolean = false; + protected var _combinedGroundAngle:Number = 0; + + /** + * Creates a new hero object. + */ + public function Hero(name:String, params:Object = null) + { + updateCallEnabled = true; + _preContactCallEnabled = true; + _beginContactCallEnabled = true; + _endContactCallEnabled = true; + + super(name, params); + + onJump = new Signal(); + onGiveDamage = new Signal(); + onTakeDamage = new Signal(); + onAnimationChange = new Signal(); + } + + override public function destroy():void + { + clearTimeout(_hurtTimeoutID); + onJump.removeAll(); + onGiveDamage.removeAll(); + onTakeDamage.removeAll(); + onAnimationChange.removeAll(); + + super.destroy(); + } + + /** + * Whether or not the player can move and jump with the hero. + */ + public function get controlsEnabled():Boolean + { + return _controlsEnabled; + } + + public function set controlsEnabled(value:Boolean):void + { + _controlsEnabled = value; + + if (!_controlsEnabled) + _fixture.SetFriction(_friction); + } + + /** + * Returns true if the hero is on the ground and can jump. + */ + public function get onGround():Boolean + { + return _onGround; + } + + /** + * The Hero uses the enemyClass parameter to know who he can kill (and who can kill him). + * Use this setter to to pass in which base class the hero's enemy should be, in String form + * or Object notation. + * For example, if you want to set the "Enemy" class as your hero's enemy, pass + * "citrus.objects.platformer.Enemy", or Enemy (with no quotes). Only String + * form will work when creating objects via a level editor. + */ + [Inspectable(defaultValue="citrus.objects.platformer.box2d.Enemy",type="String")] + public function set enemyClass(value:*):void + { + if (value is String) + _enemyClass = getDefinitionByName(value as String) as Class; + else if (value is Class) + _enemyClass = value; + } + + /** + * This is the amount of friction that the hero will have. Its value is multiplied against the + * friction value of other physics objects. + */ + public function get friction():Number + { + return _friction; + } + + [Inspectable(defaultValue="0.75")] + public function set friction(value:Number):void + { + _friction = value; + + if (_fixture) + { + _fixture.SetFriction(_friction); + } + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + // we get a reference to the actual velocity vector + var velocity:b2Vec2 = _body.GetLinearVelocity(); + + if (controlsEnabled) + { + var moveKeyPressed:Boolean = false; + + _ducking = (_ce.input.isDoing("down", inputChannel) && _onGround && canDuck); + + if (_ce.input.isDoing("right", inputChannel) && !_ducking) + { + velocity.Add(getSlopeBasedMoveAngle()); + moveKeyPressed = true; + } + + if (_ce.input.isDoing("left", inputChannel) && !_ducking) + { + velocity.Subtract(getSlopeBasedMoveAngle()); + moveKeyPressed = true; + } + + //If player just started moving the hero this tick. + if (moveKeyPressed && !_playerMovingHero) + { + _playerMovingHero = true; + _fixture.SetFriction(0); //Take away friction so he can accelerate. + } + //Player just stopped moving the hero this tick. + else if (!moveKeyPressed && _playerMovingHero) + { + _playerMovingHero = false; + _fixture.SetFriction(_friction); //Add friction so that he stops running + } + + if (_onGround && _ce.input.justDid("jump", inputChannel) && !_ducking) + { + velocity.y = -jumpHeight; + onJump.dispatch(); + _onGround = false; // also removed in the handleEndContact. Useful here if permanent contact e.g. box on hero. + } + + if (_ce.input.isDoing("jump", inputChannel) && !_onGround && velocity.y < 0) + { + velocity.y -= jumpAcceleration; + } + + if (_springOffEnemy != -1) + { + if (_ce.input.isDoing("jump", inputChannel)) + velocity.y = -enemySpringJumpHeight; + else + velocity.y = -enemySpringHeight; + _springOffEnemy = -1; + } + + //Cap velocities + if (velocity.x > (maxVelocity)) + velocity.x = maxVelocity; + else if (velocity.x < (-maxVelocity)) + velocity.x = -maxVelocity; + } + + updateAnimation(); + } + + /** + * Returns the absolute walking speed, taking moving platforms into account. + * Isn't super performance-light, so use sparingly. + */ + public function getWalkingSpeed():Number + { + var groundVelocityX:Number = 0; + for each (var groundContact:b2Fixture in _groundContacts) + { + groundVelocityX += groundContact.GetBody().GetLinearVelocity().x; + } + + return _body.GetLinearVelocity().x - groundVelocityX; + } + + /** + * Hurts the hero, disables his controls for a little bit, and dispatches the onTakeDamage signal. + */ + public function hurt():void + { + _hurt = true; + controlsEnabled = false; + _hurtTimeoutID = setTimeout(endHurtState, hurtDuration); + onTakeDamage.dispatch(); + + //Makes sure that the hero is not frictionless while his control is disabled + if (_playerMovingHero) + { + _playerMovingHero = false; + _fixture.SetFriction(_friction); + } + } + + override protected function defineBody():void + { + super.defineBody(); + + _bodyDef.fixedRotation = true; + _bodyDef.allowSleep = false; + } + + override protected function createShape():void + { + _shape = Box2DShapeMaker.BeveledRect(_width, _height, 0.1); + } + + override protected function defineFixture():void + { + super.defineFixture(); + _fixtureDef.friction = _friction; + _fixtureDef.restitution = 0; + _fixtureDef.filter.categoryBits = PhysicsCollisionCategories.Get("GoodGuys"); + _fixtureDef.filter.maskBits = PhysicsCollisionCategories.GetAll(); + } + + override public function handlePreSolve(contact:b2Contact, oldManifold:b2Manifold):void { + + if (!_ducking) + return; + + var other:IBox2DPhysicsObject = Box2DUtils.CollisionGetOther(this, contact); + + var heroTop:Number = y; + var objectBottom:Number = other.y + (other.height / 2); + + if (objectBottom < heroTop) + contact.SetEnabled(false); + } + + override public function handleBeginContact(contact:b2Contact):void { + + var collider:IBox2DPhysicsObject = Box2DUtils.CollisionGetOther(this, contact); + + if (_enemyClass && collider is _enemyClass) + { + if (_body.GetLinearVelocity().y < killVelocity && !_hurt) + { + hurt(); + + //fling the hero + var hurtVelocity:b2Vec2 = _body.GetLinearVelocity(); + hurtVelocity.y = -hurtVelocityY; + hurtVelocity.x = hurtVelocityX; + if (collider.x > x) + hurtVelocity.x = -hurtVelocityX; + _body.SetLinearVelocity(hurtVelocity); + } + else + { + _springOffEnemy = collider.y - height; + onGiveDamage.dispatch(); + } + } + + //Collision angle if we don't touch a Sensor. + if (contact.GetManifold().m_localPoint && !(collider is Sensor)) //The normal property doesn't come through all the time. I think doesn't come through against sensors. + { + var collisionAngle:Number = Math.atan2(contact.normal.y, contact.normal.x); + + if (collisionAngle >= Math.PI*.25 && collisionAngle <= 3*Math.PI*.25 ) // normal angle between pi/4 and 3pi/4 + { + _groundContacts.push(collider.body.GetFixtureList()); + _onGround = true; + updateCombinedGroundAngle(); + } + } + } + + + override public function handleEndContact(contact:b2Contact):void { + + var collider:IBox2DPhysicsObject = Box2DUtils.CollisionGetOther(this, contact); + + //Remove from ground contacts, if it is one. + var index:int = _groundContacts.indexOf(collider.body.GetFixtureList()); + if (index != -1) + { + _groundContacts.splice(index, 1); + if (_groundContacts.length == 0) + _onGround = false; + updateCombinedGroundAngle(); + } + } + + protected function getSlopeBasedMoveAngle():b2Vec2 + { + return Box2DUtils.Rotateb2Vec2(new b2Vec2(acceleration, 0), _combinedGroundAngle); + } + + protected function updateCombinedGroundAngle():void + { + _combinedGroundAngle = 0; + + if (_groundContacts.length == 0) + return; + + for each (var contact:b2Fixture in _groundContacts) { + + var angle:Number = contact.GetBody().GetAngle(); + var turn:Number = 45 * Math.PI / 180; + angle = angle % turn; + _combinedGroundAngle += angle; + } + + _combinedGroundAngle /= _groundContacts.length; + } + + protected function endHurtState():void { + + _hurt = false; + controlsEnabled = true; + } + + protected function updateAnimation():void { + + var prevAnimation:String = _animation; + + var walkingSpeed:Number = getWalkingSpeed(); + + if (_hurt) + _animation = "hurt"; + + else if (!_onGround) { + + _animation = "jump"; + + if (walkingSpeed < -acceleration) + _inverted = true; + else if (walkingSpeed > acceleration) + _inverted = false; + + } else if (_ducking) + _animation = "duck"; + + else { + + if (walkingSpeed < -acceleration) { + _inverted = true; + _animation = "walk"; + + } else if (walkingSpeed > acceleration) { + + _inverted = false; + _animation = "walk"; + + } else + _animation = "idle"; + } + + if (prevAnimation != _animation) + onAnimationChange.dispatch(); + } + } +} diff --git a/src/citrus/objects/platformer/box2d/Hills.as b/src/citrus/objects/platformer/box2d/Hills.as new file mode 100644 index 00000000..07a0de7d --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Hills.as @@ -0,0 +1,227 @@ +package citrus.objects.platformer.box2d { + + import Box2D.Collision.Shapes.b2PolygonShape; + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.b2Body; + import Box2D.Dynamics.b2BodyDef; + import Box2D.Dynamics.b2FixtureDef; + + import citrus.objects.Box2DPhysicsObject; + + /** + * This class creates perpetual hills like the games Tiny Wings, Ski Safari... + * Write a class to manage graphics, and extends this one to call graphics function. + * For more information, check out CE's Tiny Wings example. + * Thanks to Lorenzo Nuvoletta. + * Thanks to Emanuele Feronato. + */ + public class Hills extends Box2DPhysicsObject { + + /** + * This is the height of a slice. + */ + public var sliceHeight:uint = 240; + + /** + * This is the width of a slice. + */ + public var sliceWidth:uint = 30; + + /** + * This is the height of the first point. + */ + public var hillStartY:Number = 0; + + /** + * This is the width of the hills visible. Most of the time your stage width. + */ + public var widthHills:Number = 550; + + /** + * This is the factor that defined the roundness of the hills. + */ + public var roundFactor:uint = 10; + + + /** + * This is the physics object from which the Hills read its position and create/delete hills. + */ + public var rider:Box2DPhysicsObject; + + protected var _slicesCreated:uint; + protected var _randomHeight:Number = 0; + protected var _upAmplitude:Number; + protected var _downAmplitude:Number; + protected var _currentYPoint:Number = 0; + protected var _nextYPoint:Number = 0; + protected var _slicesInCurrentHill:uint; + protected var _indexSliceInCurrentHill:uint; + protected var _slices:Vector.; + protected var _sliceVectorConstructor:Vector.; + protected var _realHeight:Number = 240; + protected var _realWidth:Number = 0; + + public function Hills(name:String, params:Object = null) { + + updateCallEnabled = true; + + super(name, params); + } + + override public function initialize(poolObjectParams:Object = null):void { + + super.initialize(poolObjectParams); + } + + override public function addPhysics():void + { + super.addPhysics(); + _prepareSlices(); + } + + protected function _prepareSlices():void { + + _slices = new Vector.(); + + // Generate a line made of b2Vec2 + _sliceVectorConstructor = new Vector.(); + _sliceVectorConstructor.push(new b2Vec2(0, _realHeight)); + _sliceVectorConstructor.push(new b2Vec2(sliceWidth/_box2D.scale, _realHeight)); + + // fill the stage with slices of hills + for (var i:uint = 0; i < widthHills / sliceWidth * 1.5; ++i) { + _createSlice(); + } + } + + protected function _createSlice():void { + // Every time a new hill has to be created this algorithm predicts where the slices will be positioned + if (_indexSliceInCurrentHill >= _slicesInCurrentHill) { + hillStartY += _randomHeight; + + if(roundFactor == 0) ++roundFactor; + + _upAmplitude = 0; + _downAmplitude = 0; + + var hillWidth:Number = sliceWidth * roundFactor + Math.ceil(Math.random() * roundFactor) * sliceWidth; + + _slicesInCurrentHill = hillWidth / sliceWidth; + if(_slicesInCurrentHill % 2 != 0) ++_slicesInCurrentHill; + + _indexSliceInCurrentHill = 0; + + if (_realWidth > 0) + { + do { + _upAmplitude = Math.random() * hillWidth / 7.5; + } while (Math.abs(_realHeight + _upAmplitude) > 600); + + do { + _downAmplitude = Math.random() * hillWidth / 7.5; + } while (Math.abs(_realHeight - _downAmplitude) < 10); + } else { + _upAmplitude = 0; + _downAmplitude = 0; + } + + _realWidth += hillWidth; + + _randomHeight = _upAmplitude; + _realHeight += _upAmplitude; + _realHeight -= _downAmplitude; + hillStartY -= _randomHeight; + } + + + if (_indexSliceInCurrentHill == _slicesInCurrentHill / 2) + { + hillStartY -= _upAmplitude; + _randomHeight = _downAmplitude; + hillStartY += _randomHeight; + } + + // Calculate the position slice + _currentYPoint = _sliceVectorConstructor[0].y = (hillStartY + _randomHeight * Math.cos(2 * Math.PI / _slicesInCurrentHill * _indexSliceInCurrentHill)) / _box2D.scale; + _nextYPoint =_sliceVectorConstructor[1].y = (hillStartY + _randomHeight * Math.cos(2 * Math.PI / _slicesInCurrentHill * (_indexSliceInCurrentHill+1))) / _box2D.scale; + + var slicePolygon:b2PolygonShape = new b2PolygonShape(); + slicePolygon.SetAsVector(_sliceVectorConstructor, 2); + + _bodyDef = new b2BodyDef(); + _bodyDef.position.Set(_slicesCreated * sliceWidth/_box2D.scale, 0); + + var sliceFixture:b2FixtureDef = new b2FixtureDef(); + sliceFixture.shape = slicePolygon; + + _body = _box2D.world.CreateBody(_bodyDef); + _body.SetUserData(this); + _body.CreateFixture(sliceFixture); + _pushHill(); + } + + protected function _pushHill():void { + _slicesCreated++; + _indexSliceInCurrentHill++; + _slices.push(_body); + } + + protected function _checkHills():void { + + if (!rider) + rider = _ce.state.getFirstObjectByType(Hero) as Hero; + + var length:uint = _slices.length; + + for (var i:uint = 0; i < length; ++i) { + + if (rider.x - _slices[i].GetPosition().x*_box2D.scale > widthHills/2) { + + _deleteHill(i); + --i; + _createSlice(); + + } else + break; + } + } + + protected function _deleteHill(index:uint):void + { + _box2D.world.DestroyBody(_slices[index]); + _slices[index] = null; + _slices.splice(index, 1); + } + + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + + _checkHills(); + } + + /** + * Bodies are generated automatically, those functions aren't needed. + */ + override protected function defineBody():void + { + } + + override protected function createBody():void + { + } + + override protected function createShape():void + { + } + + + override protected function defineFixture():void + { + } + + override protected function createFixture():void + { + } + } +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/Missile.as b/src/citrus/objects/platformer/box2d/Missile.as new file mode 100644 index 00000000..a7c5b1a0 --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Missile.as @@ -0,0 +1,178 @@ +package citrus.objects.platformer.box2d +{ + + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.b2FilterData; + + import citrus.objects.Box2DPhysicsObject; + import citrus.physics.PhysicsCollisionCategories; + import citrus.physics.box2d.Box2DUtils; + import citrus.physics.box2d.IBox2DPhysicsObject; + + import org.osflash.signals.Signal; + + import flash.utils.clearTimeout; + import flash.utils.setTimeout; + + /** + * A missile is an object that moves at a particular trajectory and speed, and explodes when it comes into contact with something. + * Often you will want the object that it exploded on to also die (or at least get hurt), such as a hero or an enemy. + * Since the missile can potentially be used for any purpose, by default the missiles do not do any damage or kill the object that + * they collide with. You will have to handle this manually using the onExplode() handler. + * + *
      Properties: + *
    • angle - In degrees, the angle that the missile will fire at. Right is zero degrees, going clockwise.
    • + *
    • speed - The speed that the missile moves at.
    • + *
    • fuseDuration - In milliseconds, how long the missile lasts before it explodes if it doesn't touch anything.
    • + *
    • explodeDuration - In milliseconds, how long the explode animation lasts before the missile object is destroyed.
    + * + *
      Events: + *
    • onExplode - Dispatched when the missile explodes. Passes two parameters: + * 1. The Missile (Missile) + * 2. The Object it exploded on (Box2DPhysicsObject)
    + */ + public class Missile extends Box2DPhysicsObject + { + /** + * The speed that the missile moves at. + */ + [Inspectable(defaultValue="2")] + public var speed:Number = 2; + + /** + * In degrees, the angle that the missile will fire at. Right is zero degrees, going clockwise. + */ + [Inspectable(defaultValue="0")] + public var angle:Number = 0; + + /** + * In milliseconds, how long the explode animation lasts before the missile object is destroyed. + */ + [Inspectable(defaultValue="1000")] + public var explodeDuration:Number = 1000; + + /** + * In milliseconds, how long the missile lasts before it explodes if it doesn't touch anything. + */ + [Inspectable(defaultValue="10000")] + public var fuseDuration:Number = 10000; + + /** + * Dispatched when the missile explodes. Passes two parameters: + * 1. The Missile (Missile) + * 2. The Object it exploded on (Box2DPhysicsObject) + */ + public var onExplode:Signal; + + protected var _velocity:b2Vec2; + protected var _exploded:Boolean = false; + protected var _explodeTimeoutID:uint = 0; + protected var _fuseDurationTimeoutID:uint = 0; + protected var _contact:IBox2DPhysicsObject; + + public function Missile(name:String, params:Object = null) + { + updateCallEnabled = true; + _beginContactCallEnabled = true; + + super(name, params); + + onExplode = new Signal(Missile, Box2DPhysicsObject); + + _velocity = new b2Vec2(speed, 0); + _velocity = Box2DUtils.Rotateb2Vec2(_velocity, angle * Math.PI / 180); + _inverted = speed < 0; + } + + override public function addPhysics():void { + super.addPhysics(); + + _fuseDurationTimeoutID = setTimeout(explode, fuseDuration); + _body.SetLinearVelocity(_velocity); + + updateAnimation(); + } + + override public function destroy():void + { + onExplode.removeAll(); + clearTimeout(_explodeTimeoutID); + clearTimeout(_fuseDurationTimeoutID); + + super.destroy(); + } + + override public function get rotation():Number + { + return angle; + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + var removeGravity:b2Vec2 = new b2Vec2(); + removeGravity.Subtract(_box2D.world.GetGravity()); + removeGravity.Multiply(body.GetMass()); + + _body.ApplyForce(removeGravity, _body.GetWorldCenter()); + + if (_exploded) + _body.SetLinearVelocity(new b2Vec2()); + else + _body.SetLinearVelocity(_velocity); + } + + /** + * Explodes the missile, it shouldn't collide with anything anymore. + */ + public function explode():void + { + if (_exploded) + return; + + _exploded = true; + + updateAnimation(); + + //Not collideable with anything anymore. + var filter:b2FilterData = new b2FilterData(); + filter.maskBits = PhysicsCollisionCategories.GetNone(); + _fixture.SetFilterData(filter); + + onExplode.dispatch(this, _contact); + + clearTimeout(_fuseDurationTimeoutID); + _explodeTimeoutID = setTimeout(killMissile, explodeDuration); + } + + override protected function defineBody():void + { + super.defineBody(); + _bodyDef.bullet = true; + _bodyDef.angle = angle * Math.PI / 180; + _bodyDef.fixedRotation = true; + _bodyDef.allowSleep = false; + } + + override public function handleBeginContact(contact:b2Contact):void { + + _contact = Box2DUtils.CollisionGetOther(this, contact); + + if (!contact.GetFixtureA().IsSensor() && !contact.GetFixtureB().IsSensor()) + explode(); + } + + protected function updateAnimation():void + { + _animation = _exploded ? "exploded" : "normal"; + } + + protected function killMissile():void + { + kill = true; + } + } + +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/MovingPlatform.as b/src/citrus/objects/platformer/box2d/MovingPlatform.as new file mode 100644 index 00000000..c60491f2 --- /dev/null +++ b/src/citrus/objects/platformer/box2d/MovingPlatform.as @@ -0,0 +1,208 @@ +package citrus.objects.platformer.box2d { + + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.b2Body; + + import citrus.math.MathVector; + import citrus.physics.box2d.Box2DUtils; + + /** + * A platform that moves between two points. The MovingPlatform has several properties that can customize it. + * + *
      Properties: + *
    • speed - The speed at which the moving platform travels.
    • + *
    • enabled - Whether or not the MovingPlatform can move, no matter the condition.
    • + *
    • startX - The initial starting X position of the MovingPlatform, and the place it returns to when it reaches the end destination.
    • + *
    • startY - The initial starting Y position of the MovingPlatform, and the place it returns to when it reaches the end destination.
    • + *
    • endX - The ending X position of the MovingPlatform, and the place it returns to when it reaches the start destination.
    • + *
    • endY - The ending Y position of the MovingPlatform, and the place it returns to when it reaches the start destination.
    • + *
    • waitForPassenger - If set to true, MovingPlatform will not move unless there is a passenger. If set to false, it continually moves.
    + */ + public class MovingPlatform extends Platform + { + /** + * The speed at which the moving platform travels. + */ + [Inspectable(defaultValue="1")] + public var speed:Number = 1; + + /** + * Whether or not the MovingPlatform can move, no matter the condition. + */ + [Inspectable(defaultValue="true")] + public var enabled:Boolean = true; + + /** + * If set to true, the MovingPlatform will not move unless there is a passenger. + */ + [Inspectable(defaultValue="false")] + public var waitForPassenger:Boolean = false; + + protected var _start:MathVector = new MathVector(); + protected var _end:MathVector = new MathVector(); + protected var _forward:Boolean = true; + protected var _passengers:Vector. = new Vector.(); + + public function MovingPlatform(name:String, params:Object=null) + { + updateCallEnabled = true; + _beginContactCallEnabled = true; + _endContactCallEnabled = true; + + super(name, params); + } + + override public function destroy():void + { + _passengers.length = 0; + + super.destroy(); + } + + override public function set x(value:Number):void + { + super.x = value; + + _start.x = value / _box2D.scale; + } + + override public function set y(value:Number):void + { + super.y = value; + + _start.y = value / _box2D.scale; + } + + /** + * The initial starting X position of the MovingPlatform, and the place it returns to when it reaches + * the end destination. + */ + public function get startX():Number + { + return _start.x * _box2D.scale; + } + + [Inspectable(defaultValue="0")] + public function set startX(value:Number):void + { + _start.x = value / _box2D.scale; + } + + /** + * The initial starting Y position of the MovingPlatform, and the place it returns to when it reaches + * the end destination. + */ + public function get startY():Number + { + return _start.y * _box2D.scale; + } + + [Inspectable(defaultValue="0")] + public function set startY(value:Number):void + { + _start.y = value / _box2D.scale; + } + + /** + * The ending X position of the MovingPlatform. + */ + public function get endX():Number + { + return _end.x * _box2D.scale; + } + + [Inspectable(defaultValue="0")] + public function set endX(value:Number):void + { + _end.x = value / _box2D.scale; + } + + /** + * The ending Y position of the MovingPlatform. + */ + public function get endY():Number + { + return _end.y * _box2D.scale; + } + + [Inspectable(defaultValue="0")] + public function set endY(value:Number):void + { + _end.y = value / _box2D.scale; + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + var velocity:b2Vec2 = _body.GetLinearVelocity(); + + if ((waitForPassenger && _passengers.length == 0) || !enabled) + //Platform should not move + velocity.SetZero(); + + else { + + //Move the platform according to its destination + var destination:b2Vec2 = _forward ? new b2Vec2(_end.x, _end.y) : new b2Vec2(_start.x, _start.y); + + destination.Subtract(_body.GetPosition()); + velocity = destination; + + if (velocity.Length() > speed / _box2D.scale) { + + //Still has further to go. Normalize the velocity to the max speed + velocity.Normalize(); + velocity.Multiply(speed); + } + + else { + + //Destination is very close. Switch the travelling direction + _forward = !_forward; + + //prevent bodies to fall if they are on a edge. + var passenger:b2Body; + for each (passenger in _passengers) + passenger.SetLinearVelocity(velocity); + } + } + + _body.SetLinearVelocity(velocity); + + //prevent bodies to fall if they are on a edge. + var passengerVelocity:b2Vec2; + for each (passenger in _passengers) { + + if (velocity.y > 0) { + + passengerVelocity = passenger.GetLinearVelocity(); + // we don't change x velocity because of the friction! + passengerVelocity.y += velocity.y; + passenger.SetLinearVelocity(passengerVelocity); + } + } + + } + + override protected function defineBody():void + { + super.defineBody(); + _bodyDef.type = b2Body.b2_kinematicBody; //Kinematic bodies don't respond to outside forces, only velocity. + _bodyDef.allowSleep = false; + } + + + override public function handleBeginContact(contact:b2Contact):void { + + _passengers.push(Box2DUtils.CollisionGetOther(this, contact).body); + } + + + override public function handleEndContact(contact:b2Contact):void { + + _passengers.splice(_passengers.indexOf(Box2DUtils.CollisionGetOther(this, contact).body), 1); + } + } +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/Platform.as b/src/citrus/objects/platformer/box2d/Platform.as new file mode 100644 index 00000000..1d4e687d --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Platform.as @@ -0,0 +1,80 @@ +package citrus.objects.platformer.box2d { + + import Box2D.Collision.b2Manifold; + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.b2Body; + + import citrus.objects.Box2DPhysicsObject; + import citrus.physics.box2d.Box2DUtils; + import citrus.physics.box2d.IBox2DPhysicsObject; + + /** + * A Platform is a rectangular object that is meant to be stood on. It can be given any position, width, height, or rotation to suit your level's needs. + * You can make your platform a "one-way" or "cloud" platform so that you can jump on from underneath (collision is only applied when coming from above it). + * + * There are two ways of adding graphics for your platform. You can give your platform a graphic just like you would any other object (by passing a graphical + * class into the view property) or you can leave your platform invisible and line it up with your backgrounds for a more custom look. + * + *
      Properties: + *
    • oneWay - Makes the platform only collidable when falling from above it.
    + */ + public class Platform extends Box2DPhysicsObject { + + private var _oneWay:Boolean = false; + + public function Platform(name:String, params:Object = null) { + super(name, params); + } + + /** + * Makes the platform only collidable when falling from above it. + */ + public function get oneWay():Boolean { + return _oneWay; + } + + [Inspectable(defaultValue="false")] + public function set oneWay(value:Boolean):void { + if (_oneWay == value) + return; + + _oneWay = _preContactCallEnabled = value; + } + + override protected function defineBody():void { + super.defineBody(); + + _bodyDef.type = b2Body.b2_staticBody; + } + + override protected function defineFixture():void { + super.defineFixture(); + + _fixtureDef.restitution = 0; + } + + override public function handlePreSolve(contact:b2Contact, oldManifold:b2Manifold):void { + + if (_oneWay) { + + // Get the half-height of the collider, if we can guess what it is (we are hoping the collider extends PhysicsObject). + var colliderHalfHeight:Number = 0; + var collider:IBox2DPhysicsObject = Box2DUtils.CollisionGetOther(this, contact); + if (collider.height) + colliderHalfHeight = collider.height / 2; + else + return; + + // Get the y position of the bottom of the collider + var colliderBottom:Number = collider.y + colliderHalfHeight; + + // Hipotetic line scope related with the plataform + var slope:Number = Math.sin(_body.GetAngle()) / Math.cos(_body.GetAngle()); + + // Collider bottom should be greater than slope function + half of the plataform heigh + if (colliderBottom >= ((slope * (collider.x - x)) + y) - height / 2) + contact.SetEnabled(false); + } + } + } +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/RevolvingPlatform.as b/src/citrus/objects/platformer/box2d/RevolvingPlatform.as new file mode 100644 index 00000000..3e6bcb67 --- /dev/null +++ b/src/citrus/objects/platformer/box2d/RevolvingPlatform.as @@ -0,0 +1,139 @@ +package citrus.objects.platformer.box2d { + + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.b2Body; + + import citrus.math.MathVector; + import citrus.physics.box2d.Box2DUtils; + + /** + * A platform that rotates around a specified point + */ + public class RevolvingPlatform extends Platform { + + /** + * The speed at which the revolving platform travels. + */ + [Inspectable(defaultValue="1")] + public var speed:Number = 5; + + protected var _startAngle:Number = 0; + protected var _accAngle:Number = 0; + protected var _xOffset:Number = 60; + // distance from center on the x axis + protected var _yOffset:Number = 60; + // distance from center on the y axis + protected var _center:MathVector = new MathVector(); + protected var _passengers:Vector. = new Vector.(); + + public function RevolvingPlatform(name:String, params:Object = null) { + + updateCallEnabled = true; + _beginContactCallEnabled = true; + _endContactCallEnabled = true; + + super(name, params); + } + + override public function destroy():void { + _passengers.length = 0; + + super.destroy(); + } + + override public function set x(value:Number):void { + super.x = value; + + centerX = value; + } + + override public function set y(value:Number):void { + super.y = value; + + centerY = value; + } + + [Inspectable(defaultValue="0")] + public function set startAngle(value:Number):void { + _startAngle = value * Math.PI / 180; + _accAngle = _startAngle; + } + + [Inspectable(defaultValue="0")] + public function set centerX(value:Number):void { + _center.x = value / _box2D.scale; + } + + [Inspectable(defaultValue="0")] + public function set centerY(value:Number):void { + _center.y = value / _box2D.scale; + } + + public function get xOffset():Number { + return _xOffset * _box2D.scale; + } + + [Inspectable(defaultValue="60")] + public function set xOffset(value:Number):void { + _xOffset = value / _box2D.scale; + } + + public function get yOffset():Number { + return _yOffset * _box2D.scale; + } + + [Inspectable(defaultValue="60")] + public function set yOffset(value:Number):void { + _yOffset = value / _box2D.scale; + } + + override protected function defineBody():void { + super.defineBody(); + + _bodyDef.type = b2Body.b2_kinematicBody; + _bodyDef.allowSleep = false; + } + + override public function handleBeginContact(contact:b2Contact):void { + + _passengers.push(Box2DUtils.CollisionGetOther(this, contact).body); + } + + override public function handleEndContact(contact:b2Contact):void { + + _passengers.splice(_passengers.indexOf(Box2DUtils.CollisionGetOther(this, contact).body), 1); + } + + override public function update(timeDelta:Number):void { + + var platformVec:b2Vec2; + var differenceVec:b2Vec2; + var passengerVec:b2Vec2; + + super.update(timeDelta); + + _accAngle += timeDelta; + + // calculate new position + platformVec = new b2Vec2(); + platformVec.x = _center.x + Math.sin(_accAngle * speed) * _xOffset; + platformVec.y = _center.y + Math.cos(_accAngle * speed) * _yOffset; + + // get the difference between the new position and the current position + differenceVec = platformVec.Copy(); + differenceVec.Subtract(_body.GetPosition()); + + // update passenger positions to account for platform's motion + for each (var b:b2Body in _passengers) { + passengerVec = b.GetPosition(); + passengerVec.Add(differenceVec); + b.SetPosition(passengerVec); + } + + // update platform's position + _body.SetPosition(platformVec); + } + } + +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/Reward.as b/src/citrus/objects/platformer/box2d/Reward.as new file mode 100644 index 00000000..87883e6b --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Reward.as @@ -0,0 +1,184 @@ +package citrus.objects.platformer.box2d +{ + + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.b2Fixture; + import Box2D.Dynamics.b2FixtureDef; + + import citrus.math.MathVector; + import citrus.objects.Box2DPhysicsObject; + import citrus.physics.PhysicsCollisionCategories; + import citrus.physics.box2d.Box2DUtils; + import citrus.physics.box2d.IBox2DPhysicsObject; + + import org.osflash.signals.Signal; + + import flash.geom.Point; + import flash.utils.getDefinitionByName; + + /** + * The Reward class is meant to pop out of a RewardBox when the player bumps it. A Reward object is the equivalent of a "mushroom" + * "fire flower", or "invincible star" in the Mario games. + * + *

    For each reward that you want in your game, you should make a class that extends this Reward class. If you want an ExtraLifeReward, + * you should make a class called ExtraLifeReward that extends Reward. Then hardcode your view, speed, impulseX, and impulseY properties. + * Of course, you can also add additional functionality as well by doing this.

    + * + *

    When you create a RewardBox, you will pass the name of this class into the rewardClass property of RewardBox. That will make the RewardBox + * generate a Reward.

    + * + *
      Properties: + *
    • speed : set the speed that the reward moves at.
    • + *
    • impulseX and impulseY : make the reward "jump" out of the box.
    • + *
    • collectorClass : tell the object who can collect it. It is set to Hero class by default.
    + * + *
      Events: + *
    • onCollect : the Signal is dispatched when the reward is collected. Since the RewardBox generates the reward, you probably won't + * get a reference to the reward. Thus, you can instead listen for RewardBox.onRewardCollect to find out when the reward is collected. Nevertheless, + * if you listen for Reward.OnCollect, it passes a reference to itself when it dispatches.
    + * + *
      Animation: + *
    • The reward object only has a default animation.
    + * + */ + public class Reward extends Box2DPhysicsObject + { + /** + * The speed at which the reward moves. It will turn around when it hits a wall. + */ + [Inspectable(defaultValue="1")] + public var speed:Number = 1; + + /** + * The speed on the x axis that the reward will fly out of the box. + */ + [Inspectable(defaultValue="0")] + public var impulseX:Number = 0; + + /** + * The speed on the y axis that the reward will fly out of the box. + */ + [Inspectable(defaultValue="-10")] + public var impulseY:Number = -10; + + /** + * Dispatches when the reward gets collected. Also see RewardBox.onRewardCollect for a possibly more convenient event. + */ + public var onCollect:Signal; + + protected var _collectFixtureDef:b2FixtureDef; + protected var _collectFixture:b2Fixture; + + protected var _movingLeft:Boolean = false; + protected var _collectorClass:Class = Hero; + protected var _isNew:Boolean = true; + + public function Reward(name:String, params:Object = null) + { + updateCallEnabled = true; + _beginContactCallEnabled = true; + + super(name, params); + + onCollect = new Signal(Reward); + } + + override public function destroy():void + { + onCollect.removeAll(); + + super.destroy(); + } + + /** + * Specify the class of the object that you want the reward to be collected by. + * You can specify the collectorClass in String form (collectorClass = "com.myGame.MyHero") or via direct reference + * (collectorClass = MyHero). You should use the String form when creating Rewards in an external level editor. Make sure and + * specify the entire classpath. + */ + public function get collectorClass():* + { + return _collectorClass; + } + + [Inspectable(defaultValue="citrus.objects.platformer.box2d.Hero",type="String")] + public function set collectorClass(value:*):void + { + if (value is String) + _collectorClass = getDefinitionByName(value) as Class; + else if (value is Class) + _collectorClass = value; + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + var velocity:b2Vec2 = _body.GetLinearVelocity(); + + if (_isNew) + { + _isNew = false; + velocity.x += impulseX; + velocity.y += impulseY; + } + else + { + if (_movingLeft) + velocity.x = -speed; + else + velocity.x = speed; + } + } + + override protected function defineBody():void + { + super.defineBody(); + + _bodyDef.fixedRotation = true; + } + + override protected function defineFixture():void + { + super.defineFixture(); + _fixtureDef.friction = 0; + _fixtureDef.restitution = 0; + _fixtureDef.filter.categoryBits = PhysicsCollisionCategories.Get("Items"); + _fixtureDef.filter.maskBits = PhysicsCollisionCategories.GetAllExcept("GoodGuys", "BadGuys"); + + _collectFixtureDef = new b2FixtureDef(); + _collectFixtureDef.shape = _shape; + _collectFixtureDef.isSensor = true; + _collectFixtureDef.filter.categoryBits = PhysicsCollisionCategories.Get("Items"); + _collectFixtureDef.filter.maskBits = PhysicsCollisionCategories.GetAllExcept("BadGuys"); + } + + override protected function createFixture():void + { + super.createFixture(); + + _collectFixture = _body.CreateFixture(_collectFixtureDef); + } + + override public function handleBeginContact(contact:b2Contact):void { + + var collider:IBox2DPhysicsObject = Box2DUtils.CollisionGetOther(this, contact); + + if (collider is _collectorClass) + { + kill = true; + onCollect.dispatch(this); + } + + if (contact.GetManifold().m_localPoint) + { + var normalPoint:Point = new Point(contact.GetManifold().m_localPoint.x, contact.GetManifold().m_localPoint.y); + var collisionAngle:Number = new MathVector(normalPoint.x, normalPoint.y).angle * 180 / Math.PI; + if (collisionAngle < 45 || collisionAngle > 135) + _movingLeft = !_movingLeft; + } + } + } + +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/RewardBox.as b/src/citrus/objects/platformer/box2d/RewardBox.as new file mode 100644 index 00000000..b8066945 --- /dev/null +++ b/src/citrus/objects/platformer/box2d/RewardBox.as @@ -0,0 +1,174 @@ +package citrus.objects.platformer.box2d +{ + + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.b2Body; + + import citrus.math.MathVector; + import citrus.objects.Box2DPhysicsObject; + + import org.osflash.signals.Signal; + + import flash.geom.Point; + import flash.utils.getDefinitionByName; + + /** + * The RewardBox is a special type of platform that you can "bump" to make a reward come out. It is meant to be similar + * to those "question blocks" or "mystery blocks" in mario. + * + *
      Params: + *
    • rewardClass : it specifies what kind of reward to have the box create. The reward class object + * that is generated must extend the "Reward" class.
    • + *
    • "collision normal" angle : it specifies the angle that you must come at it in order for it to generate a reward. The default is 90, + * which is "from below", as long as the box is not rotated.
    + * + *

    This means that you must also create a class that extends Reward for every reward type that you want in your game. + * If you were making a mario clone, you would make a FireFlowerReward. This is where you would specify the reward's graphics, + * its initial impulse out of the box, and any custom code such as unique movement or a death timer.

    + * + *
      Animations: + *
    • Your Reward box should have a "normal" and "used" animation state. Once the box's reward has been obtained, it cannot be used again.
    + * + *
      Events: + *
    • onUse : gets dispatched when the reward box gets bumped. It passes a reference of itself.
    • + *
    • onRewardCollect : gets dispatched when the reward is collected. This is where you would + * write the code to grant your player the reward (such as a greater jump height, more points, or another life).
    + * + *
      Other: + *
    • If you don't want the reward box to generate a reward, (or you want the reward to be granted immediately, like points), + * you can set the rewardClass to null and just listen for the "onUse" event to grant the player the reward.
    + */ + public class RewardBox extends Box2DPhysicsObject + { + /** + * This is the vector normal that the reward box must be collided with in order for the reward to be created. + * On a box with no rotation, 90 is "from below", 0 is "from the right", -180 is "from the left", and -90 is "from above". + */ + [Inspectable(defaultValue="90")] + public var collisionAngle:Number = 90; + + /** + * Dispatched when the box gets "bumped" or used. + */ + public var onUse:Signal; + + /** + * Dispatched when the reward that came out of the box is collected by the player. + */ + public var onRewardCollect:Signal; + + protected var _rewardClass:Class = Reward; + protected var _isUsed:Boolean = false; + protected var _createReward:Boolean = false; + + public function RewardBox(name:String, params:Object = null) + { + updateCallEnabled = true; + _beginContactCallEnabled = true; + + super(name, params); + + onUse = new Signal(RewardBox); + onRewardCollect = new Signal(RewardBox, Reward); + } + + override public function destroy():void + { + onUse.removeAll(); + onRewardCollect.removeAll(); + + super.destroy(); + } + + override public function get animation():String + { + if (_isUsed) + { + return "used"; + } + return "normal"; + } + + /** + * Specify the class of the object that you want the reward box to generate. The class must extend Reward in order to be valid. + * You can specify the rewardClass in String form (rewardClass = "com.myGame.FireballReward") or via direct reference + * (rewardClass = FireballReward). You should use the String form when creating RewardBoxes in an external level editor. Make sure and + * specify the entire classpath. + */ + public function get rewardClass():* + { + return _rewardClass; + } + + [Inspectable(defaultValue="citrus.objects.platformer.box2d.Reward",type="String")] + public function set rewardClass(value:*):void + { + if (value is String) + _rewardClass = getDefinitionByName(value) as Class; + else if (value is Class) + _rewardClass = value; + else + _rewardClass = null; + } + + public function get isUsed():Boolean + { + return _isUsed; + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + if (_createReward) + { + _createReward = false; + + //You can make the rewardClass property null if you just want to listen for the bump event and not have it generate a reward. + if (_rewardClass) + { + var rewardObject:Reward = new _rewardClass(name + "Reward"); + rewardObject.onCollect.addOnce(handleRewardCollected); + rewardObject.x = x; + rewardObject.y = y - ((height / 2) + (rewardObject.height / 2) + 1); + _ce.state.add(rewardObject); + } + + onUse.dispatch(this); + _isUsed = true; + } + } + + override protected function defineBody():void + { + super.defineBody(); + _bodyDef.type = b2Body.b2_staticBody; + } + + override protected function defineFixture():void + { + super.defineFixture(); + _fixtureDef.restitution = 0; + } + + override public function handleBeginContact(contact:b2Contact):void { + + if (contact.GetManifold().m_localPoint) + { + var normalPoint:Point = new Point(contact.GetManifold().m_localPoint.x, contact.GetManifold().m_localPoint.y); + var collisionAngle:Number = new MathVector(normalPoint.x, normalPoint.y).angle * 180 / Math.PI; + if (collisionAngle == -90) + { + _beginContactCallEnabled = false; + _createReward = true; + } + } + } + + protected function handleRewardCollected(reward:Reward):void + { + onRewardCollect.dispatch(this, reward); + } + } + +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/Sensor.as b/src/citrus/objects/platformer/box2d/Sensor.as new file mode 100644 index 00000000..22be9d53 --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Sensor.as @@ -0,0 +1,77 @@ +package citrus.objects.platformer.box2d { + + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.b2Body; + + import citrus.objects.Box2DPhysicsObject; + + import org.osflash.signals.Signal; + + /** + * Sensors simply listen for when an object begins and ends contact with them. They dispatch a signal + * when contact is made or ended, and this signal can be used to perform custom game logic such as + * triggering a scripted event, ending a level, popping up a dialog box, and virtually anything else. + * + *

    Remember that signals dispatch events when ANY Box2D object collides with them, so you will want + * your collision handler to ignore collisions with objects that it is not interested in, or extend + * the sensor and use maskBits to ignore collisions altogether.

    + * + *
      Events: + *
    • onBeginContact : Dispatches on first contact with the sensor.
    • + *
    • onEndContact : Dispatches when the object leaves the sensor.
    + */ + public class Sensor extends Box2DPhysicsObject + { + /** + * Determine if the sensor is used as a ladder. Ladder handler isn't implemented in the Citrus Engine to keep the Hero class easily readable. + */ + public var isLadder:Boolean = false; + + /** + * Dispatches on first contact with the sensor. + */ + public var onBeginContact:Signal; + /** + * Dispatches when the object leaves the sensor. + */ + public var onEndContact:Signal; + + public function Sensor(name:String, params:Object=null) + { + _beginContactCallEnabled = true; + _endContactCallEnabled = true; + + super(name, params); + onBeginContact = new Signal(b2Contact); + onEndContact = new Signal(b2Contact); + } + + override public function destroy():void + { + onBeginContact.removeAll(); + onEndContact.removeAll(); + + super.destroy(); + } + + override protected function defineBody():void + { + super.defineBody(); + _bodyDef.type = b2Body.b2_staticBody; + } + + override protected function defineFixture():void + { + super.defineFixture(); + _fixtureDef.isSensor = true; + } + + override public function handleBeginContact(contact:b2Contact):void { + onBeginContact.dispatch(contact); + } + + override public function handleEndContact(contact:b2Contact):void { + onEndContact.dispatch(contact); + } + } +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/Teleporter.as b/src/citrus/objects/platformer/box2d/Teleporter.as new file mode 100644 index 00000000..3578162a --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Teleporter.as @@ -0,0 +1 @@ +package citrus.objects.platformer.box2d { import Box2D.Dynamics.Contacts.b2Contact; import citrus.objects.Box2DPhysicsObject; import flash.utils.clearTimeout; import flash.utils.setTimeout; /** * A Teleporter, moves an object to a destination. The waiting time is more or less long. * It is a Sensor which can be activate after a contact. *
      Properties: *
    • endX : the object's x destination after teleportation.
    • *
    • endY : the object's y destination after teleportation.
    • *
    • object : the PhysicsObject teleported.
    • *
    • waitingTime : how many time before teleportation, master ?
    • *
    • teleport : set it to true to teleport your object.
    */ public class Teleporter extends Sensor { /** * the object's x destination after teleportation. */ [Inspectable(defaultValue="0")] public var endX:Number = 0; /** * the object's y destination after teleportation. */ [Inspectable(defaultValue="0")] public var endY:Number = 0; /** * the PhysicsObject teleported. */ [Inspectable(defaultValue="",type="String")] public var object:Box2DPhysicsObject; /** * how many time before teleportation, master ? */ [Inspectable(defaultValue="0")] public var waitingTime:Number = 0; /** * set it to true to teleport your object. */ public var teleport:Boolean = false; protected var _teleporting:Boolean = false; protected var _teleportTimeoutID:uint; public function Teleporter(name:String, params:Object = null) { updateCallEnabled = true; super(name, params); } override public function destroy():void { clearTimeout(_teleportTimeoutID); super.destroy(); } override public function update(timeDelta:Number):void { super.update(timeDelta); if (teleport) { _teleporting = true; _updateAnimation(); _teleportTimeoutID = setTimeout(_teleport, waitingTime); teleport = false; } } override public function handleBeginContact(contact:b2Contact):void { onBeginContact.dispatch(contact); teleport = true; } protected function _teleport():void { _teleporting = false; _updateAnimation(); object.x = endX; object.y = endY; clearTimeout(_teleportTimeoutID); } protected function _updateAnimation():void { _animation = _teleporting ? "teleport" : "normal"; } } } \ No newline at end of file diff --git a/src/citrus/objects/platformer/box2d/Treadmill.as b/src/citrus/objects/platformer/box2d/Treadmill.as new file mode 100644 index 00000000..b96fb25f --- /dev/null +++ b/src/citrus/objects/platformer/box2d/Treadmill.as @@ -0,0 +1 @@ +package citrus.objects.platformer.box2d { import Box2D.Dynamics.b2Body; /** * A Treadmill is a MovingPlatform with some new options. *
      Properties: *
    • speedTread : the speed of the tread.
    • *
    • startingDirection : the tread's direction.
    • *
    • enableTreadmill : activate it or not.
    */ public class Treadmill extends MovingPlatform { /** * The speed of the tread. */ [Inspectable(defaultValue="3")] public var speedTread:Number = 3; /** * The tread's direction. */ [Inspectable(defaultValue="right",enumeration="right,left")] public var startingDirection:String = "right"; /** * Activate it or not. */ [Inspectable(defaultValue="true")] public var enableTreadmill:Boolean = true; public function Treadmill(name:String, params:Object = null) { super(name, params); if (startingDirection == "left") _inverted = true; } override public function destroy():void { super.destroy(); } override public function update(timeDelta:Number):void { super.update(timeDelta); if (enableTreadmill) { for each (var passengers:b2Body in _passengers) { if (startingDirection == "right") passengers.GetUserData().x += speedTread; else passengers.GetUserData().x -= speedTread; } } _updateAnimation(); } protected function _updateAnimation():void { _animation = enableTreadmill ? "move" : "normal"; } } } \ No newline at end of file diff --git a/src/citrus/objects/platformer/nape/Cannon.as b/src/citrus/objects/platformer/nape/Cannon.as new file mode 100644 index 00000000..819307b8 --- /dev/null +++ b/src/citrus/objects/platformer/nape/Cannon.as @@ -0,0 +1,154 @@ +package citrus.objects.platformer.nape { + + import citrus.objects.NapePhysicsObject; + + import org.osflash.signals.Signal; + + import flash.display.MovieClip; + import flash.events.TimerEvent; + import flash.utils.Timer; + + /** + * A cannon is an object which fires missiles. A cannon is a static body so it extends Platform. + * Properties: + * fireRate : The frequency that missiles are fired. + * startingDirection : The direction that missiles are fired. + * openFire : Indicate if the cannon shoot at start or not. + * + * Events: + * onGiveDamage - Dispatched when the missile explodes on a PhysicsObject. Passes one parameter: + * The Object it exploded on (PhysicsObject) + */ + public class Cannon extends Platform { + + /** + * The frequency that missiles are fired. + */ + [Inspectable(defaultValue="2000")] + public var fireRate:Number = 2000; + + /** + * The direction that missiles are fired + */ + [Inspectable(defaultValue="right",enumeration="right,left")] + public var startingDirection:String = "right"; + + /** + * Indicate if the cannon shoot at start or not. + */ + [Inspectable(defaultValue="true")] + public var openFire:Boolean = true; + + [Inspectable(defaultValue="20")] + public var missileWidth:uint = 20; + + [Inspectable(defaultValue="20")] + public var missileHeight:uint = 20; + + [Inspectable(defaultValue="60")] + public var missileSpeed:Number = 60; + + [Inspectable(defaultValue="0")] + public var missileAngle:Number = 0; + + [Inspectable(defaultValue="1000")] + public var missileExplodeDuration:Number = 1000; + + [Inspectable(defaultValue="10000")] + public var missileFuseDuration:Number = 10000; + + [Inspectable(defaultValue="",format="File",type="String")] + public var missileView:* = MovieClip; + + /** + * onGiveDamage - Dispatched when the missile explodes on a PhysicsObject. Passes one parameter: + * The Object it exploded on (PhysicsObject) + */ + public var onGiveDamage:Signal; + + protected var _firing:Boolean = false; + + protected var _timer:Timer; + + public function Cannon(name:String, params:Object = null) { + + super(name, params); + + onGiveDamage = new Signal(NapePhysicsObject); + } + + override public function initialize(poolObjectParams:Object = null):void { + + super.initialize(poolObjectParams); + + if (openFire) + startFire(); + } + + override public function destroy():void { + + onGiveDamage.removeAll(); + _ce.onPlayingChange.remove(_playingChanged); + + _timer.stop(); + _timer.removeEventListener(TimerEvent.TIMER, _fire); + + super.destroy(); + } + + protected function _damage(missile:Missile, contact:NapePhysicsObject):void { + + if (contact != null) + onGiveDamage.dispatch(contact); + } + + public function startFire():void { + + _firing = true; + _updateAnimation(); + + _timer = new Timer(fireRate); + _timer.addEventListener(TimerEvent.TIMER, _fire); + _timer.start(); + + _ce.onPlayingChange.add(_playingChanged); + } + + public function stopFire():void { + + _firing = false; + _updateAnimation(); + + _timer.stop(); + _timer.removeEventListener(TimerEvent.TIMER, _fire); + + _ce.onPlayingChange.remove(_playingChanged); + } + + protected function _fire(tEvt:TimerEvent):void { + + var missile:Missile; + + if (startingDirection == "right") + missile = new Missile("Missile", {x:x + width, y:y, width:missileWidth, height:missileHeight, speed:missileSpeed, angle:missileAngle, explodeDuration:missileExplodeDuration, fuseDuration:missileFuseDuration, view:missileView}); + else + missile = new Missile("Missile", {x:x - width, y:y, width:missileWidth, height:missileHeight, speed:-missileSpeed, angle:missileAngle, explodeDuration:missileExplodeDuration, fuseDuration:missileFuseDuration, view:missileView}); + + _ce.state.add(missile); + missile.onExplode.addOnce(_damage); + } + + protected function _updateAnimation():void { + + _animation = _firing ? "fire" : "normal"; + } + + /** + * Start or stop the timer. Automatically called by the engine when the game is paused/unpaused. + */ + protected function _playingChanged(playing:Boolean):void { + + playing ? _timer.start() : _timer.stop(); + } + } +} diff --git a/src/citrus/objects/platformer/nape/Coin.as b/src/citrus/objects/platformer/nape/Coin.as new file mode 100644 index 00000000..be94450a --- /dev/null +++ b/src/citrus/objects/platformer/nape/Coin.as @@ -0,0 +1,46 @@ +package citrus.objects.platformer.nape { + + import citrus.physics.nape.NapeUtils; + + import nape.callbacks.InteractionCallback; + + import flash.utils.getDefinitionByName; + + /** + * Coin is basically a sensor that destroys itself when a particular class type touches it. + */ + public class Coin extends Sensor { + + protected var _collectorClass:Class = Hero; + + public function Coin(name:String, params:Object = null) { + + super(name, params); + } + + /** + * The Coin uses the collectorClass parameter to know who can collect it. + * Use this setter to pass in which base class the collector should be, in String form + * or Object notation. + * For example, if you want to set the "Hero" class as your hero's enemy, pass + * "citrus.objects.platformer.nape.Hero" or Hero directly (no quotes). Only String + * form will work when creating objects via a level editor. + */ + [Inspectable(defaultValue="citrus.objects.platformer.nape.Hero")] + public function set collectorClass(value:*):void { + + if (value is String) + _collectorClass = getDefinitionByName(value as String) as Class; + else if (value is Class) + _collectorClass = value; + } + + override public function handleBeginContact(interactionCallback:InteractionCallback):void { + + super.handleBeginContact(interactionCallback); + + if (_collectorClass && NapeUtils.CollisionGetOther(this, interactionCallback) is _collectorClass) + kill = true; + } + } +} diff --git a/src/citrus/objects/platformer/nape/Crate.as b/src/citrus/objects/platformer/nape/Crate.as new file mode 100644 index 00000000..fd0cffe6 --- /dev/null +++ b/src/citrus/objects/platformer/nape/Crate.as @@ -0,0 +1,30 @@ +package citrus.objects.platformer.nape { + + import citrus.objects.NapePhysicsObject; + + /** + * An object made for Continuous Collision Detection. It should only be used for very fast, small moving dynamic bodies. + */ + public class Crate extends NapePhysicsObject { + + public function Crate(name:String, params:Object = null) { + super(name, params); + } + + override protected function createBody():void { + + super.createBody(); + + _body.isBullet = true; + } + + override protected function createMaterial():void { + + super.createMaterial(); + + _material.density = 0.3; + _material.elasticity = 0; + } + + } +} diff --git a/src/citrus/objects/platformer/nape/Enemy.as b/src/citrus/objects/platformer/nape/Enemy.as new file mode 100644 index 00000000..2c09d230 --- /dev/null +++ b/src/citrus/objects/platformer/nape/Enemy.as @@ -0,0 +1,166 @@ +package citrus.objects.platformer.nape { + + import citrus.objects.NapePhysicsObject; + import citrus.physics.PhysicsCollisionCategories; + import citrus.physics.nape.NapeUtils; + + import nape.callbacks.CbType; + import nape.callbacks.InteractionCallback; + import nape.dynamics.InteractionFilter; + import nape.geom.Vec2; + + import flash.utils.clearTimeout; + import flash.utils.getDefinitionByName; + import flash.utils.setTimeout; + + /** + * This is a common example of a side-scrolling bad guy. He has limited logic, basically + * only turning around when he hits a wall. + * + * When controlling collision interactions between two objects, such as a Hero and Enemy, + * I like to let each object perform its own actions, not control one object's action from the other object. + * For example, the Hero doesn't contain the logic for killing the Enemy, and the Enemy doesn't contain the + * logic for making the hero "Spring" when he kills him. + */ + public class Enemy extends NapePhysicsObject { + + public static const ENEMY:CbType = new CbType(); + + [Inspectable(defaultValue="51.8")] + public var speed:Number = 51.8; + + [Inspectable(defaultValue="-90")] + public var enemyKillVelocity:Number = -90; + + [Inspectable(defaultValue="left",enumeration="left,right")] + public var startingDirection:String = "left"; + + [Inspectable(defaultValue="400")] + public var hurtDuration:Number = 400; + + [Inspectable(defaultValue="-100000")] + public var leftBound:Number = -100000; + + [Inspectable(defaultValue="100000")] + public var rightBound:Number = 100000; + + protected var _hurtTimeoutID:uint = 0; + protected var _hurt:Boolean = false; + protected var _enemyClass:* = Hero; + + public function Enemy(name:String, params:Object=null) { + + updateCallEnabled = true; + _beginContactCallEnabled = true; + + super(name, params); + + if (startingDirection == "left") + _inverted = true; + } + + override public function destroy():void { + + clearTimeout(_hurtTimeoutID); + + super.destroy(); + } + + public function get enemyClass():* { + return _enemyClass; + } + + [Inspectable(defaultValue="citrus.objects.platformer.nape.Hero",type="String")] + public function set enemyClass(value:*):void { + if (value is String) + _enemyClass = getDefinitionByName(value) as Class; + else if (value is Class) + _enemyClass = value; + } + + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + + var position:Vec2 = _body.position; + + //Turn around when they pass their left/right bounds + if ((_inverted && position.x < leftBound) || (!_inverted && position.x > rightBound)) + turnAround(); + + var velocity:Vec2 = _body.velocity; + + if (!_hurt) + velocity.x = _inverted ? -speed : speed; + else + velocity.x = 0; + + updateAnimation(); + } + + /** + * The enemy is hurt, start the time out with hurtDuration value. Then it called endHurtState's function. + */ + public function hurt():void { + + _hurt = true; + _hurtTimeoutID = setTimeout(endHurtState, hurtDuration); + } + + /** + * Change enemy's direction + */ + public function turnAround():void { + + _inverted = !_inverted; + } + + override protected function createBody():void { + + super.createBody(); + + _body.allowRotation = false; + } + + override protected function createFilter():void { + + _body.setShapeFilters(new InteractionFilter(PhysicsCollisionCategories.Get("BadGuys"), PhysicsCollisionCategories.GetAll())); + } + + override protected function createConstraint():void { + + _body.space = _nape.space; + _body.cbTypes.add(ENEMY); + } + + override public function handleBeginContact(callback:InteractionCallback):void { + + var collider:NapePhysicsObject = NapeUtils.CollisionGetOther(this, callback); + + if (callback.arbiters.length > 0 && callback.arbiters.at(0).collisionArbiter) { + + var collisionAngle:Number = callback.arbiters.at(0).collisionArbiter.normal.angle * 180 / Math.PI; + + if (collider is _enemyClass && collider.body.velocity.y != 0 && collider.body.velocity.y > enemyKillVelocity) + hurt(); + else if ((collider is Platform && collisionAngle != 90) || collider is Enemy) + turnAround(); + } + } + + protected function updateAnimation():void { + + _animation = _hurt ? "die" : "walk"; + } + + /** + * The enemy is no more hurt, but it is killed. Override this function to prevent enemy's death. + */ + protected function endHurtState():void { + + _hurt = false; + kill = true; + } + + } +} diff --git a/src/citrus/objects/platformer/nape/Hero.as b/src/citrus/objects/platformer/nape/Hero.as new file mode 100644 index 00000000..179fc598 --- /dev/null +++ b/src/citrus/objects/platformer/nape/Hero.as @@ -0,0 +1,461 @@ +package citrus.objects.platformer.nape { + + import citrus.objects.NapePhysicsObject; + import citrus.physics.PhysicsCollisionCategories; + import citrus.physics.nape.NapeUtils; + + import nape.callbacks.CbType; + import nape.callbacks.InteractionCallback; + import nape.dynamics.InteractionFilter; + import nape.geom.Vec2; + + import org.osflash.signals.Signal; + + import flash.utils.clearTimeout; + import flash.utils.getDefinitionByName; + import flash.utils.setTimeout; + + /** + * This is a common, simple, yet solid implementation of a side-scrolling Hero. + * The hero can run, jump, get hurt, and kill enemies. It dispatches signals + * when significant events happen. The game state's logic should listen for those signals + * to perform game state updates (such as increment coin collections). + * + * Don't store data on the hero object that you will need between two or more levels (such + * as current coin count). The hero should be re-created each time a state is created or reset. + */ + public class Hero extends NapePhysicsObject { + + public static const HERO:CbType = new CbType(); + + //properties + /** + * This is the rate at which the hero speeds up when you move him left and right. + */ + [Inspectable(defaultValue="30")] + public var acceleration:Number = 30; + + /** + * This is the fastest speed that the hero can move left or right. + */ + [Inspectable(defaultValue="240")] + public var maxVelocity:Number = 240; + + /** + * This is the initial velocity that the hero will move at when he jumps. + */ + [Inspectable(defaultValue="330")] + public var jumpHeight:Number = 330; + + /** + * This is the amount of "float" that the hero has when the player holds the jump button while jumping. + */ + [Inspectable(defaultValue="9")] + public var jumpAcceleration:Number = 9; + + /** + * This is the y velocity that the hero must be travelling in order to kill an Enemy. + */ + [Inspectable(defaultValue="-90")] + public var killVelocity:Number = -90; + + /** + * The y velocity that the hero will spring when he kills an enemy. + */ + [Inspectable(defaultValue="240")] + public var enemySpringHeight:Number = 240; + + /** + * The y velocity that the hero will spring when he kills an enemy while pressing the jump button. + */ + [Inspectable(defaultValue="270")] + public var enemySpringJumpHeight:Number = 270; + + /** + * How long the hero is in hurt mode for. + */ + [Inspectable(defaultValue="1000")] + public var hurtDuration:Number = 1000; + + /** + * The amount of kick-back that the hero jumps when he gets hurt. + */ + [Inspectable(defaultValue="180")] + public var hurtVelocityX:Number = 180; + + /** + * The amount of kick-back that the hero jumps when he gets hurt. + */ + [Inspectable(defaultValue="300")] + public var hurtVelocityY:Number = 300; + + /** + * Determines whether or not the hero's ducking ability is enabled. + */ + [Inspectable(defaultValue="true")] + public var canDuck:Boolean = true; + + /** + * Defines which input Channel to listen to. + */ + [Inspectable(defaultValue = "0")] + public var inputChannel:uint = 0; + + // events + /** + * Dispatched whenever the hero jumps. + */ + public var onJump:Signal; + + /** + * Dispatched whenever the hero gives damage to an enemy. + */ + public var onGiveDamage:Signal; + + /** + * Dispatched whenever the hero takes damage from an enemy. + */ + public var onTakeDamage:Signal; + + /** + * Dispatched whenever the hero's animation changes. + */ + public var onAnimationChange:Signal; + + protected var _groundContacts:Array = [];// Used to determine if he's on ground or not. + protected var _enemyClass:Class = Enemy; + protected var _onGround:Boolean = false; + protected var _springOffEnemy:Number = -1; + protected var _hurtTimeoutID:uint; + protected var _hurt:Boolean = false; + protected var _dynamicFriction:Number = 0.77; + protected var _staticFriction:Number = 1.2; + protected var _playerMovingHero:Boolean = false; + protected var _controlsEnabled:Boolean = true; + protected var _ducking:Boolean = false; + protected var _combinedGroundAngle:Number = 0; + + public function Hero(name:String, params:Object = null) { + + updateCallEnabled = true; + _beginContactCallEnabled = true; + _endContactCallEnabled = true; + + super(name, params); + + onJump = new Signal(); + onGiveDamage = new Signal(); + onTakeDamage = new Signal(); + onAnimationChange = new Signal(); + } + + override protected function createConstraint():void { + + super.createConstraint(); + + _body.cbTypes.add(HERO); + } + + override public function destroy():void { + + clearTimeout(_hurtTimeoutID); + onJump.removeAll(); + onGiveDamage.removeAll(); + onTakeDamage.removeAll(); + onAnimationChange.removeAll(); + + super.destroy(); + } + + /** + * Whether or not the player can move and jump with the hero. + */ + public function get controlsEnabled():Boolean { + return _controlsEnabled; + } + + public function set controlsEnabled(value:Boolean):void { + _controlsEnabled = value; + + if (!_controlsEnabled) { + _material.dynamicFriction = _dynamicFriction; + _material.staticFriction = _staticFriction; + } + } + + /** + * Returns true if the hero is on the ground and can jump. + */ + public function get onGround():Boolean { + return _onGround; + } + + /** + * The Hero uses the enemyClass parameter to know who he can kill (and who can kill him). + * Use this setter to to pass in which base class the hero's enemy should be, in String form + * or Object notation. + * For example, if you want to set the "Enemy" class as your hero's enemy, pass + * "citrus.objects.platformer.Enemy", or Enemy (with no quotes). Only String + * form will work when creating objects via a level editor. + */ + [Inspectable(defaultValue="citrus.objects.platformer.nape.Enemy",type="String")] + public function set enemyClass(value:*):void { + + if (value is String) + _enemyClass = getDefinitionByName(value as String) as Class; + else if (value is Class) + _enemyClass = value; + } + + /** + * This is the amount of friction that the hero will have. Its value is multiplied against the + * friction value of other dynamic physics objects. + */ + public function get dynamicFriction():Number { + return _dynamicFriction; + } + + [Inspectable(defaultValue="0.77")] + public function set dynamicFriction(value:Number):void { + + _material.dynamicFriction = _dynamicFriction = value; + } + + /** + * This is the amount of friction that the hero will have. Its value is multiplied against the + * friction value of other static physics objects. + */ + public function get staticFriction():Number { + return _staticFriction; + } + + [Inspectable(defaultValue="1.2")] + public function set staticFriction(value:Number):void { + + _material.staticFriction = _staticFriction = value; + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + // we get a reference to the actual velocity vector + var velocity:Vec2 = _body.velocity; + + if (controlsEnabled) + { + var moveKeyPressed:Boolean = false; + + _ducking = (_ce.input.isDoing("down", inputChannel) && _onGround && canDuck); + + if (_ce.input.isDoing("right", inputChannel) && !_ducking) + { + //velocity.addeq(getSlopeBasedMoveAngle()); + velocity.x += acceleration; + moveKeyPressed = true; + } + + if (_ce.input.isDoing("left", inputChannel) && !_ducking) + { + //velocity.subeq(getSlopeBasedMoveAngle()); + velocity.x -= acceleration; + moveKeyPressed = true; + } + + //If player just started moving the hero this tick. + if (moveKeyPressed && !_playerMovingHero) + { + _playerMovingHero = true; + _material.dynamicFriction = 0; //Take away friction so he can accelerate. + _material.staticFriction = 0; + } + //Player just stopped moving the hero this tick. + else if (!moveKeyPressed && _playerMovingHero) + { + _playerMovingHero = false; + _material.dynamicFriction = _dynamicFriction; //Add friction so that he stops running + _material.staticFriction = _staticFriction; + } + + if (_onGround && _ce.input.justDid("jump", inputChannel) && !_ducking) + { + velocity.y = -jumpHeight; + onJump.dispatch(); + _onGround = false; // also removed in the handleEndContact. Useful here if permanent contact e.g. box on hero. + } + + if (_ce.input.isDoing("jump", inputChannel) && !_onGround && velocity.y < 0) + { + velocity.y -= jumpAcceleration; + } + + if (_springOffEnemy != -1) + { + if (_ce.input.isDoing("jump", inputChannel)) + velocity.y = -enemySpringJumpHeight; + else + velocity.y = -enemySpringHeight; + _springOffEnemy = -1; + } + + //Cap velocities + if (velocity.x > (maxVelocity)) + velocity.x = maxVelocity; + else if (velocity.x < (-maxVelocity)) + velocity.x = -maxVelocity; + } + + updateAnimation(); + } + + protected function getSlopeBasedMoveAngle():Vec2 { + + return new Vec2(acceleration, 0); + //return new Vec2(acceleration, 0).rotate(_combinedGroundAngle); + } + + /** + * Hurts the hero, disables his controls for a little bit, and dispatches the onTakeDamage signal. + */ + public function hurt():void + { + _hurt = true; + controlsEnabled = false; + _hurtTimeoutID = setTimeout(endHurtState, hurtDuration); + onTakeDamage.dispatch(); + + //Makes sure that the hero is not frictionless while his control is disabled + if (_playerMovingHero) + { + _playerMovingHero = false; + _material.dynamicFriction = _dynamicFriction; + } + } + + override protected function createBody():void { + + super.createBody(); + + _body.allowRotation = false; + } + + override protected function createMaterial():void { + + super.createMaterial(); + _material.staticFriction = 0; + _material.elasticity = 0; + } + + override protected function createFilter():void { + + _body.setShapeFilters(new InteractionFilter(PhysicsCollisionCategories.Get("GoodGuys"), PhysicsCollisionCategories.GetAll())); + } + + override public function handleBeginContact(callback:InteractionCallback):void { + + var collider:NapePhysicsObject = NapeUtils.CollisionGetOther(this, callback); + + if (_enemyClass && collider is _enemyClass) + { + if ((_body.velocity.y == 0 || _body.velocity.y < killVelocity) && !_hurt) + { + hurt(); + + //fling the hero + var hurtVelocity:Vec2 = _body.velocity; + hurtVelocity.y = -hurtVelocityY; + hurtVelocity.x = hurtVelocityX; + if (collider.x > x) + hurtVelocity.x = -hurtVelocityX; + _body.velocity = hurtVelocity; + } + else + { + _springOffEnemy = collider.y - height; + onGiveDamage.dispatch(); + } + } + + if (callback.arbiters.length > 0 && callback.arbiters.at(0).collisionArbiter) { + + var collisionAngle:Number = callback.arbiters.at(0).collisionArbiter.normal.angle * 180 / Math.PI; + + if ((collisionAngle > 45 && collisionAngle < 135) || (collisionAngle > -30 && collisionAngle < 10) || collisionAngle == -90) + { + if (collisionAngle > 1 || collisionAngle < -1) { + //we don't want the Hero to be set up as onGround if it touches a cloud. + if (collider is Platform && (collider as Platform).oneWay && collisionAngle == -90) + return; + + _groundContacts.push(collider.body); + _onGround = true; + //updateCombinedGroundAngle(); + } + } + } + } + + override public function handleEndContact(callback:InteractionCallback):void { + + var collider:NapePhysicsObject = NapeUtils.CollisionGetOther(this, callback); + + //Remove from ground contacts, if it is one. + var index:int = _groundContacts.indexOf(collider.body); + if (index != -1) + { + _groundContacts.splice(index, 1); + if (_groundContacts.length == 0) + _onGround = false; + //updateCombinedGroundAngle(); + } + } + + protected function endHurtState():void { + + _hurt = false; + controlsEnabled = true; + } + + protected function updateAnimation():void { + + var prevAnimation:String = _animation; + + //var walkingSpeed:Number = getWalkingSpeed(); + var walkingSpeed:Number = _body.velocity.x; // this won't work long term! + + if (_hurt) + _animation = "hurt"; + + else if (!_onGround) { + + _animation = "jump"; + + if (walkingSpeed < -acceleration) + _inverted = true; + else if (walkingSpeed > acceleration) + _inverted = false; + + } else if (_ducking) + _animation = "duck"; + + else { + + if (walkingSpeed < -acceleration) { + _inverted = true; + _animation = "walk"; + + } else if (walkingSpeed > acceleration) { + + _inverted = false; + _animation = "walk"; + + } else + _animation = "idle"; + } + + if (prevAnimation != _animation) + onAnimationChange.dispatch(); + + } + } +} diff --git a/src/citrus/objects/platformer/nape/Hills.as b/src/citrus/objects/platformer/nape/Hills.as new file mode 100644 index 00000000..15640c49 --- /dev/null +++ b/src/citrus/objects/platformer/nape/Hills.as @@ -0,0 +1,171 @@ +package citrus.objects.platformer.nape { + + import citrus.objects.NapePhysicsObject; + + import nape.geom.Vec2; + import nape.phys.Body; + import nape.phys.BodyType; + import nape.shape.Polygon; + + /** + * This class creates perpetual hills like the games Tiny Wings, Ski Safari... + * Write a class to manage graphics, and extends this one to call graphics function. + * For more information, check out CE's Tiny Wings example. + * Thanks to Lorenzo Nuvoletta. + */ + public class Hills extends NapePhysicsObject { + + /** + * This is the height of a slice. + */ + public var sliceHeight:uint = 600; + + /** + * This is the width of a slice. + */ + public var sliceWidth:uint = 30; + + /** + * This is the height of the first point. + */ + public var currentYPoint:Number = 200; + + /** + * This is the width of the hills visible. Most of the time your stage width. + */ + public var widthHills:Number = 550; + + /** + * This is the physics object from which the Hills read its position and create/delete hills. + */ + public var rider:NapePhysicsObject; + + protected var _slicesCreated:uint; + protected var _currentAmplitude:Number; + protected var _nextYPoint:Number; + protected var _slicesInCurrentHill:uint; + protected var _indexSliceInCurrentHill:uint; + protected var _slices:Vector.; + protected var _sliceVectorConstructor:Vector.; + + public function Hills(name:String, params:Object = null) { + + updateCallEnabled = true; + + super(name, params); + } + + override public function initialize(poolObjectParams:Object = null):void { + + super.initialize(poolObjectParams); + } + + override public function addPhysics():void + { + super.addPhysics(); + _prepareSlices(); + } + + protected function _prepareSlices():void { + + _slices = new Vector.(); + + // Generate a rectangle made of Vec2 + _sliceVectorConstructor = new Vector.(); + _sliceVectorConstructor.push(new Vec2(0, sliceHeight)); + _sliceVectorConstructor.push(new Vec2(0, 0)); + _sliceVectorConstructor.push(new Vec2(sliceWidth, 0)); + _sliceVectorConstructor.push(new Vec2(sliceWidth, sliceHeight)); + + // fill the stage with slices of hills + for (var i:uint = 0; i < widthHills / sliceWidth * 1.2; ++i) { + _createSlice(); + } + } + + protected function _createSlice():void { + + // Every time a new hill has to be created this algorithm predicts where the slices will be positioned + if (_indexSliceInCurrentHill >= _slicesInCurrentHill) { + _slicesInCurrentHill = Math.random() * 40 + 10; + _currentAmplitude = Math.random() * 60 - 20; + _indexSliceInCurrentHill = 0; + } + // Calculate the position of the next slice + _nextYPoint = currentYPoint + (Math.sin(((Math.PI / _slicesInCurrentHill) * _indexSliceInCurrentHill)) * _currentAmplitude); + _sliceVectorConstructor[2].y = _nextYPoint - currentYPoint; + var slicePolygon:Polygon = new Polygon(_sliceVectorConstructor); + _body = new Body(BodyType.STATIC); + _body.userData.myData = this; + _body.shapes.add(slicePolygon); + _body.position.x = _slicesCreated * sliceWidth; + _body.position.y = currentYPoint; + _body.space = _nape.space; + + _pushHill(); + } + + protected function _pushHill():void { + + _slicesCreated++; + _indexSliceInCurrentHill++; + currentYPoint = _nextYPoint; + + _slices.push(_body); + } + + protected function _checkHills():void { + + if (!rider) + rider = _ce.state.getFirstObjectByType(Hero) as Hero; + + var length:uint = _slices.length; + + for (var i:uint = 0; i < length; ++i) { + + if (rider.body.position.x - _slices[i].position.x > widthHills * 0.5 + 100) { + + _deleteHill(i); + --i; + _createSlice(); + + } else + break; + } + } + + protected function _deleteHill(index:uint):void { + + _nape.space.bodies.remove(_slices[index]); + _slices.splice(index, 1); + } + + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + + _checkHills(); + } + + /** + * Bodies are generated automatically, those functions aren't needed. + */ + override protected function defineBody():void { + } + + override protected function createBody():void { + } + + override protected function createMaterial():void { + } + + override protected function createShape():void { + } + + override protected function createFilter():void { + } + + override protected function createConstraint():void { + } + } +} diff --git a/src/citrus/objects/platformer/nape/Missile.as b/src/citrus/objects/platformer/nape/Missile.as new file mode 100644 index 00000000..cc8e04e1 --- /dev/null +++ b/src/citrus/objects/platformer/nape/Missile.as @@ -0,0 +1,177 @@ +package citrus.objects.platformer.nape { + + import citrus.objects.NapePhysicsObject; + import citrus.physics.PhysicsCollisionCategories; + import citrus.physics.nape.NapeUtils; + + import nape.callbacks.CbType; + import nape.callbacks.InteractionCallback; + import nape.dynamics.InteractionFilter; + import nape.geom.Vec2; + + import org.osflash.signals.Signal; + + import flash.utils.clearTimeout; + import flash.utils.setTimeout; + + /** + * A missile is an object that moves at a particular trajectory and speed, and explodes when it comes into contact with something. + * Often you will want the object that it exploded on to also die (or at least get hurt), such as a hero or an enemy. + * Since the missile can potentially be used for any purpose, by default the missiles do not do any damage or kill the object that + * they collide with. You will have to handle this manually using the onExplode() handler. + * + * Properties: + * angle - In degrees, the angle that the missile will fire at. Right is zero degrees, going clockwise. + * speed - The speed that the missile moves at. + * fuseDuration - In milliseconds, how long the missile lasts before it explodes if it doesn't touch anything. + * explodeDuration - In milliseconds, how long the explode animation lasts before the missile object is destroyed. + * + * Events + * onExplode - Dispatched when the missile explodes. Passes two parameters: + * 1. The Missile (Missile) + * 2. The Object it exploded on (PhysicsObject) + */ + public class Missile extends NapePhysicsObject { + + public static const MISSILE:CbType = new CbType(); + + /** + * The speed that the missile moves at. + */ + [Inspectable(defaultValue="60")] + public var speed:Number = 60; + + /** + * In degrees, the angle that the missile will fire at. Right is zero degrees, going clockwise. + */ + [Inspectable(defaultValue="0")] + public var angle:Number = 0; + + /** + * In milliseconds, how long the explode animation lasts before the missile object is destroyed. + */ + [Inspectable(defaultValue="1000")] + public var explodeDuration:Number = 1000; + + /** + * In milliseconds, how long the missile lasts before it explodes if it doesn't touch anything. + */ + [Inspectable(defaultValue="10000")] + public var fuseDuration:Number = 10000; + + /** + * Dispatched when the missile explodes. Passes two parameters: + * 1. The Missile (Missile) + * 2. The Object it exploded on (PhysicsObject) + */ + public var onExplode:Signal; + + protected var _velocity:Vec2; + protected var _exploded:Boolean = false; + protected var _explodeTimeoutID:uint = 0; + protected var _fuseDurationTimeoutID:uint = 0; + protected var _contact:NapePhysicsObject; + + public function Missile(name:String, params:Object = null) { + + updateCallEnabled = true; + _beginContactCallEnabled = true; + + super(name, params); + + onExplode = new Signal(Missile, NapePhysicsObject); + + _velocity = new Vec2(speed, 0); + _velocity.rotate(angle * Math.PI / 180); + _inverted = speed < 0; + } + + override public function addPhysics():void { + super.addPhysics(); + + _fuseDurationTimeoutID = setTimeout(explode, fuseDuration); + _body.velocity = _velocity; + + updateAnimation(); + } + + override public function destroy():void { + + onExplode.removeAll(); + + clearTimeout(_explodeTimeoutID); + clearTimeout(_fuseDurationTimeoutID); + + super.destroy(); + } + + override public function get rotation():Number { + return angle; + } + + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + + if (_exploded) + _body.velocity = new Vec2(); + + updateAnimation(); + } + + /** + * Explodes the missile + */ + public function explode():void { + + if (_exploded) + return; + + _exploded = true; + updateAnimation(); + + var filter:InteractionFilter = new InteractionFilter(); + filter.collisionMask = PhysicsCollisionCategories.GetNone(); + _body.setShapeFilters(filter); + + onExplode.dispatch(this, _contact); + + clearTimeout(_fuseDurationTimeoutID); + _explodeTimeoutID = setTimeout(killMissile, explodeDuration); + } + + override protected function createBody():void { + + super.createBody(); + + _body.allowRotation = false; + _body.gravMass = 0; + _body.rotate(new Vec2(_x, _y), angle * Math.PI / 180); + } + + override protected function createConstraint():void { + + _body.space = _nape.space; + _body.cbTypes.add(MISSILE); + } + + override public function handleBeginContact(callback:InteractionCallback):void { + + _contact = NapeUtils.CollisionGetOther(this, callback); + + if (!callback.arbiters.at(0).shape1.sensorEnabled && !callback.arbiters.at(0).shape2.sensorEnabled) + explode(); + } + + protected function updateAnimation():void { + + _animation = _exploded ? "exploded" : "normal"; + } + + protected function killMissile():void { + + kill = true; + } + + } +} diff --git a/src/citrus/objects/platformer/nape/MissileWithExplosion.as b/src/citrus/objects/platformer/nape/MissileWithExplosion.as new file mode 100644 index 00000000..ee2bf2a6 --- /dev/null +++ b/src/citrus/objects/platformer/nape/MissileWithExplosion.as @@ -0,0 +1,214 @@ +package citrus.objects.platformer.nape +{ + + import citrus.objects.NapePhysicsObject; + + import nape.callbacks.CbType; + import nape.callbacks.InteractionCallback; + import nape.geom.Vec2; + import nape.phys.Body; + import nape.phys.BodyList; + + import org.osflash.signals.Signal; + + import flash.display.MovieClip; + import flash.utils.clearTimeout; + import flash.utils.setTimeout; + + /** + * A missile is an object that moves at a particular trajectory and speed, and explodes when it comes into contact with something. + * Often you will want the object that it exploded on to also die (or at least get hurt), such as a hero or an enemy. + * Since the missile can potentially be used for any purpose, by default the missiles do not do any damage or kill the object that + * they collide with. You will have to handle this manually using the onExplode() handler. + * + * Properties: + * angle - In degrees, the angle that the missile will fire at. Right is zero degrees, going clockwise. + * speed - The speed that the missile moves at. + * fuseDuration - In milliseconds, how long the missile lasts before it explodes if it doesn't touch anything. + * explodeDuration - In milliseconds, how long the explode animation lasts before the missile object is destroyed. + * + * Events + * onExplode - Dispatched when the missile explodes. Passes two parameters: + * 1. The Missile (Missile) + * 2. The Object it exploded on (PhysicsObject) + */ + public class MissileWithExplosion extends NapePhysicsObject + { + public static const MISSILE:CbType = new CbType(); + /** + * The speed that the missile moves at. + */ + public var speed:Number = 200; + /** + * In degrees, the angle that the missile will fire at. Right is zero degrees, going clockwise. + */ + public var angle:Number = 0; + /** + * In milliseconds, how long the explode animation lasts before the missile object is destroyed. + */ + public var explodeDuration:Number = 1000; + /** + * In milliseconds, how long the missile lasts before it explodes if it doesn't touch anything. + */ + public var fuseDuration:Number = 10000; + /** + * Flag to determine whether explosion exerts outward force on nearby dynamic objects + */ + public var useForce:Boolean = true; + /** + * Dispatched when the missile explodes. Passes two parameters: + * 1. The Missile (Missile) + * 2. The Object it exploded on (PhysicsObject) + */ + public var onExplode:Signal; + + private var _velocity:Vec2; + private var _exploded:Boolean = false; + private var _explodeTimeoutID:uint = 0; + private var _fuseDurationTimeoutID:uint = 0; + private var _contact:NapePhysicsObject; + + public static function Make(name:String, x:Number, y:Number, width:Number, height:Number, angle:Number, view:* = null, speed:Number = 200, fuseDuration:Number = 10000, explodeDuration:Number = 1000, useForce:Boolean = true):MissileWithExplosion + { + if (view == null) view = MovieClip; + return new MissileWithExplosion(name, { x: x, y: y, width: width, height: height, angle: angle, view: view, speed: speed, fuseDuration: fuseDuration, explodeDuration: explodeDuration, useForce:useForce } ); + } + + public function MissileWithExplosion(name:String, params:Object = null) + { + super(name, params); + onExplode = new Signal(MissileWithExplosion, NapePhysicsObject); + } + + override public function initialize(poolObjectParams:Object = null):void { + + super.initialize(poolObjectParams); + + _velocity = new Vec2(speed, 0); + _velocity.rotate(angle); + _inverted = speed < 0; + + _fuseDurationTimeoutID = setTimeout(explode, fuseDuration); + _body.velocity = _velocity; + } + + override public function destroy():void + { + onExplode.removeAll(); + //_fixture.removeEventListener(ContactEvent.BEGIN_CONTACT, handleBeginContact); + clearTimeout(_explodeTimeoutID); + clearTimeout(_fuseDurationTimeoutID); + + super.destroy(); + } + + override public function get rotation():Number + { + return angle; + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + var removeGravity:Vec2 = new Vec2(); + removeGravity.subeq(_nape.gravity); + removeGravity.muleq(_body.mass); + _body.applyImpulse(removeGravity); + + if (!_exploded) + { + _body.velocity = _velocity; + } + else + { + _body.velocity = new Vec2(); + } + + updateAnimation(); + } + + /** + * Explodes the missile + */ + public function explode():void + { + if (_exploded) + return; + + _exploded = true; + + //Not collideable with anything anymore. + // FIXME need a nape alt for this command + //_fixture.SetFilterData({ maskBits: Box2DCollisionCategories.GetNone() }); + + onExplode.dispatch(this, _contact); + + clearTimeout(_fuseDurationTimeoutID); + _explodeTimeoutID = setTimeout(killMissile, explodeDuration); + + + // here we jump into the body list of the nape space, and poll for distance from bomb. If close enough push force from explosion point + if (useForce) { + + var explosionVec2:Vec2 = new Vec2(x, y); + + var bodies:BodyList = _nape.space.bodies; + var b:Body; + var ballVec2:Vec2; + var impulseVector:Vec2; + var ll:uint = bodies.length; + for (var i:int = 0; i < ll; i ++) { + b = bodies.at(i); + if (!b.isDynamic()) continue; + ballVec2 = b.position; + impulseVector = new Vec2(ballVec2.x - explosionVec2.x, ballVec2.y - explosionVec2.y); + if (impulseVector.length < 400) { + var impulseForce:Number = (400 - impulseVector.length) / 30; + var impulse:Vec2 = new Vec2(impulseVector.x * impulseForce, impulseVector.y * impulseForce * 1.4); + b.applyImpulse(impulse); + } + } + } + } + + override protected function defineBody():void + { + super.defineBody(); + } + + override protected function createBody():void + { + super.createBody(); + + _body.allowRotation = false; + } + override protected function createConstraint():void { + + _body.space = _nape.space; + _body.cbTypes.add(MISSILE); + } + override public function handleBeginContact(callback:InteractionCallback):void + { + explode(); + } + + protected function updateAnimation():void + { + if (_exploded) + { + _animation = "exploded"; + } + else + { + _animation = "normal"; + } + } + + protected function killMissile():void + { + kill = true; + } + } + +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/nape/MovingPlatform.as b/src/citrus/objects/platformer/nape/MovingPlatform.as new file mode 100644 index 00000000..8ea31cd8 --- /dev/null +++ b/src/citrus/objects/platformer/nape/MovingPlatform.as @@ -0,0 +1,243 @@ +package citrus.objects.platformer.nape { + + import citrus.math.MathVector; + import citrus.objects.NapePhysicsObject; + import citrus.objects.common.Path; + import citrus.physics.nape.NapeUtils; + + import nape.callbacks.InteractionCallback; + import nape.geom.Vec2; + import nape.phys.Body; + import nape.phys.BodyType; + + /** + * A platform that moves between two points. The MovingPlatform has several properties that can customize it. + * + *
      Properties: + *
    • speed - The speed at which the moving platform travels.
    • + *
    • enabled - Whether or not the MovingPlatform can move, no matter the condition.
    • + *
    • startX - The initial starting X position of the MovingPlatform, and the place it returns to when it reaches the end destination.
    • + *
    • startY - The initial starting Y position of the MovingPlatform, and the place it returns to when it reaches the end destination.
    • + *
    • endX - The ending X position of the MovingPlatform, and the place it returns to when it reaches the start destination.
    • + *
    • endY - The ending Y position of the MovingPlatform, and the place it returns to when it reaches the start destination.
    • + *
    • waitForPassenger - If set to true, MovingPlatform will not move unless there is a passenger. If set to false, it continually moves.
    + */ + public class MovingPlatform extends Platform + { + /** + * The speed at which the moving platform travels. + */ + [Inspectable(defaultValue="30")] + public var speed:Number = 30; + + /** + * Whether or not the MovingPlatform can move, no matter the condition. + */ + [Inspectable(defaultValue="true")] + public var enabled:Boolean = true; + + /** + * If set to true, the MovingPlatform will not move unless there is a passenger. + */ + [Inspectable(defaultValue="false")] + public var waitForPassenger:Boolean = false; + + protected var _start:MathVector = new MathVector(); + protected var _end:MathVector = new MathVector(); + protected var _forward:Boolean = true; + protected var _passengers:Vector. = new Vector.(); + + protected var _path:Path; + protected var _pathIndex:int = 0; + + public function MovingPlatform(name:String, params:Object = null) + { + updateCallEnabled = true; + _beginContactCallEnabled = true; + _endContactCallEnabled = true; + + super(name, params); + } + + public function get path():Path + { + return _path; + } + + public function set path(value:Path):void + { + _path = value; + } + + override public function set x(value:Number):void + { + super.x = value; + + _start.x = value; + } + + override public function set y(value:Number):void + { + super.y = value; + + _start.y = value; + } + + /** + * The initial starting X position of the MovingPlatform, and the place it returns to when it reaches + * the end destination. + */ + public function get startX():Number + { + return _start.x; + } + + [Inspectable(defaultValue="0")] + public function set startX(value:Number):void + { + _start.x = value; + } + + /** + * The initial starting Y position of the MovingPlatform, and the place it returns to when it reaches + * the end destination. + */ + public function get startY():Number + { + return _start.y; + } + + [Inspectable(defaultValue="0")] + public function set startY(value:Number):void + { + _start.y = value; + } + + /** + * The ending X position of the MovingPlatform. + */ + public function get endX():Number + { + return _end.x; + } + + [Inspectable(defaultValue="0")] + public function set endX(value:Number):void + { + _end.x = value; + } + + /** + * The ending Y position of the MovingPlatform. + */ + public function get endY():Number + { + return _end.y; + } + + [Inspectable(defaultValue="0")] + public function set endY(value:Number):void + { + _end.y = value; + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + var velocity:Vec2; + + if ((waitForPassenger && _passengers.length == 0) || !enabled) + { + // Platform should not move + velocity = new Vec2(); + } + else + { + // Move the platform according to its destination + var destination:Vec2; + + if (_path) + { + var dmv:MathVector = _path.getPointAt(_pathIndex); + destination = new Vec2(dmv.x, dmv.y); + } + else + { + destination = _forward ? new Vec2(_end.x, _end.y) : new Vec2(_start.x, _start.y); + } + + destination.subeq(body.position); + velocity = destination; + + if (velocity.length >= 1) + { + // Still has futher to go. Normalize the velocity to the speed + velocity.normalise(); + velocity.muleq(speed); + } + else + { + if (_path) + { + if (_path.isPolygon) + { + _pathIndex++; + + if (_pathIndex == _path.length) + { + _pathIndex = 0; + _forward = true; + } + } + else + { + if (_forward) + { + _pathIndex++; + if (_pathIndex == _path.length) + { + _forward = false; + _pathIndex = _path.length - 2; + } + } + else + { + _pathIndex--; + if (_pathIndex == -1) + { + _forward = true; + _pathIndex = 1; + } + } + } + } + else + { + _forward = !_forward; + } + } + } + + _body.velocity.set(velocity); + } + + override protected function defineBody():void + { + super.defineBody(); + _bodyType = BodyType.KINEMATIC; + } + + override public function handleBeginContact(callback:InteractionCallback):void + { + var other:NapePhysicsObject = NapeUtils.CollisionGetOther(this, callback); + _passengers.push(other.body); + } + + override public function handleEndContact(callback:InteractionCallback):void + { + var other:NapePhysicsObject = NapeUtils.CollisionGetOther(this, callback); + _passengers.splice(_passengers.indexOf(other.body), 1); + } + } +} diff --git a/src/citrus/objects/platformer/nape/Platform.as b/src/citrus/objects/platformer/nape/Platform.as new file mode 100644 index 00000000..da192006 --- /dev/null +++ b/src/citrus/objects/platformer/nape/Platform.as @@ -0,0 +1,112 @@ +package citrus.objects.platformer.nape { + + import citrus.objects.NapePhysicsObject; + import nape.callbacks.CbType; + import nape.callbacks.InteractionType; + import nape.callbacks.PreCallback; + import nape.callbacks.PreFlag; + import nape.callbacks.PreListener; + import nape.phys.BodyType; + + + /** + * A Platform is a rectangular object that is meant to be stood on. It can be given any position, width, height, or rotation to suit your level's needs. + * You can make your platform a "one-way" or "cloud" platform so that you can jump on from underneath (collision is only applied when coming from above it). + * + * There are two ways of adding graphics for your platform. You can give your platform a graphic just like you would any other object (by passing a graphical + * class into the view property) or you can leave your platform invisible and line it up with your backgrounds for a more custom look. + * + * Properties: + * oneWay - Makes the platform only collidable when falling from above it. + */ + public class Platform extends NapePhysicsObject { + + public static const ONEWAY_PLATFORM:CbType = new CbType(); + + private var _oneWay:Boolean = false; + private var _preListener:PreListener; + + public function Platform(name:String, params:Object = null) { + + super(name, params); + } + + override public function destroy():void { + + if (_preListener) + _body.space.listeners.remove(_preListener); + + super.destroy(); + } + + public function get oneWay():Boolean + { + return _oneWay; + } + + [Inspectable(defaultValue="false")] + public function set oneWay(value:Boolean):void + { + if (_oneWay == value) + return; + + _oneWay = value; + + // If the body hasn't been created yet stop here. + // The callback type and the listener will be added later by the "createConstraint"-method. + if (!_body) + return; + + if (_oneWay && !_preListener) + { + _preListener = new PreListener(InteractionType.COLLISION, Platform.ONEWAY_PLATFORM, CbType.ANY_BODY, handlePreContact,0,true); + _body.space.listeners.add(_preListener); + _body.cbTypes.add(ONEWAY_PLATFORM); + } + else + { + if (_preListener) { + _preListener.space = null; + _preListener = null; + } + _body.cbTypes.remove(ONEWAY_PLATFORM); + } + } + + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + } + + override protected function defineBody():void { + + _bodyType = BodyType.STATIC; + } + + override protected function createMaterial():void { + + super.createMaterial(); + + _material.elasticity = 0; + } + + override protected function createConstraint():void { + + super.createConstraint(); + + if (_oneWay) { + _preListener = new PreListener(InteractionType.COLLISION, ONEWAY_PLATFORM, CbType.ANY_BODY, handlePreContact,0,true); + _body.cbTypes.add(ONEWAY_PLATFORM); + _body.space.listeners.add(_preListener); + } + } + + override public function handlePreContact(callback:PreCallback):PreFlag + { + if ((callback.arbiter.collisionArbiter.normal.y > 0) != callback.swapped) + return PreFlag.IGNORE; + else + return PreFlag.ACCEPT; + } + } +} diff --git a/src/citrus/objects/platformer/nape/Sensor.as b/src/citrus/objects/platformer/nape/Sensor.as new file mode 100644 index 00000000..3e22ad2e --- /dev/null +++ b/src/citrus/objects/platformer/nape/Sensor.as @@ -0,0 +1,92 @@ +package citrus.objects.platformer.nape { + + import citrus.objects.NapePhysicsObject; + + import nape.callbacks.CbType; + import nape.callbacks.InteractionCallback; + import nape.phys.BodyType; + + import org.osflash.signals.Signal; + + /** + * Sensors simply listen for when an object begins and ends contact with them. They dispatch a signal + * when contact is made or ended, and this signal can be used to perform custom game logic such as + * triggering a scripted event, ending a level, popping up a dialog box, and virtually anything else. + * + * Remember that signals dispatch events when ANY Nape object collides with them, so you will want + * your collision handler to ignore collisions with objects that it is not interested in, or extend + * the sensor and use maskBits to ignore collisions altogether. + * + * Events + * onBeginContact - Dispatches on first contact with the sensor. + * onEndContact - Dispatches when the object leaves the sensor. + */ + public class Sensor extends NapePhysicsObject { + + public static const SENSOR:CbType = new CbType(); + + /** + * Determine if the sensor is used as a ladder. Ladder handler isn't implemented in the Citrus Engine to keep the Hero class easily readable. + */ + public var isLadder:Boolean = false; + + /** + * Dispatches on first contact with the sensor. + */ + public var onBeginContact:Signal; + /** + * Dispatches when the object leaves the sensor. + */ + public var onEndContact:Signal; + + public function Sensor(name:String, params:Object = null) { + + _beginContactCallEnabled = true; + _endContactCallEnabled = true; + + super(name, params); + + onBeginContact = new Signal(InteractionCallback); + onEndContact = new Signal(InteractionCallback); + } + + override public function destroy():void { + + onBeginContact.removeAll(); + onEndContact.removeAll(); + + super.destroy(); + } + + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + } + + override protected function defineBody():void { + + _bodyType = BodyType.STATIC; + } + + override protected function createFilter():void { + + super.createFilter(); + + _shape.sensorEnabled = true; + } + + override protected function createConstraint():void { + + _body.space = _nape.space; + _body.cbTypes.add(SENSOR); + } + + override public function handleBeginContact(interactionCallback:InteractionCallback):void { + onBeginContact.dispatch(interactionCallback); + } + + override public function handleEndContact(interactionCallback:InteractionCallback):void { + onEndContact.dispatch(interactionCallback); + } + } +} diff --git a/src/citrus/objects/platformer/nape/Teleporter.as b/src/citrus/objects/platformer/nape/Teleporter.as new file mode 100644 index 00000000..7d004d79 --- /dev/null +++ b/src/citrus/objects/platformer/nape/Teleporter.as @@ -0,0 +1,107 @@ +package citrus.objects.platformer.nape { + + import nape.callbacks.InteractionCallback; + + import citrus.objects.NapePhysicsObject; + + import citrus.physics.nape.NapeUtils; + + import flash.utils.clearTimeout; + import flash.utils.setTimeout; + + /** + * A Teleporter, moves an object to a destination. The waiting time is more or less long. + * It is a Sensor which can be activate after a contact. + *
      Properties: + *
    • endX : the object's x destination after teleportation.
    • + *
    • endY : the object's y destination after teleportation.
    • + *
    • object : the PhysicsObject teleported.
    • + *
    • waitingTime : how many time before teleportation, master ?
    • + *
    • teleport : set it to true to teleport your object.
    + */ + public class Teleporter extends Sensor { + + /** + * the object's x destination after teleportation. + */ + [Inspectable(defaultValue="0")] + public var endX:Number = 0; + + /** + * the object's y destination after teleportation. + */ + [Inspectable(defaultValue="0")] + public var endY:Number = 0; + + /** + * the PhysicsObject teleported. + */ + [Inspectable(defaultValue="",type="String")] + public var object:NapePhysicsObject; + + /** + * how many time before teleportation, master ? + */ + [Inspectable(defaultValue="0")] + public var waitingTime:Number = 0; + + /** + * set it to true to teleport your object. + */ + public var teleport:Boolean = false; + + protected var _teleporting:Boolean = false; + protected var _teleportTimeoutID:uint; + + public function Teleporter(name:String, params:Object = null) { + updateCallEnabled = true; + + super(name, params); + } + + override public function destroy():void { + clearTimeout(_teleportTimeoutID); + + super.destroy(); + } + + override public function update(timeDelta:Number):void { + super.update(timeDelta); + + if (teleport) { + + _teleporting = true; + _updateAnimation(); + + _teleportTimeoutID = setTimeout(_teleport, waitingTime); + + teleport = false; + } + } + + override public function handleBeginContact(interactionCallback:InteractionCallback):void { + super.handleBeginContact(interactionCallback); + + var contact:NapePhysicsObject = NapeUtils.CollisionGetOther(this, interactionCallback); + + if (contact is Hero) { + object = contact; + teleport = true; + } + } + + protected function _teleport():void { + _teleporting = false; + _updateAnimation(); + + object.x = endX; + object.y = endY; + + clearTimeout(_teleportTimeoutID); + } + + protected function _updateAnimation():void { + _animation = _teleporting ? "teleport" : "normal"; + } + } +} \ No newline at end of file diff --git a/src/citrus/objects/platformer/simple/DynamicObject.as b/src/citrus/objects/platformer/simple/DynamicObject.as new file mode 100644 index 00000000..0c808fe5 --- /dev/null +++ b/src/citrus/objects/platformer/simple/DynamicObject.as @@ -0,0 +1,15 @@ +package citrus.objects.platformer.simple { + + import citrus.objects.CitrusSprite; + + /** + * An object that will be moved away from overlapping during a collision (probably your hero or something else that moves). + */ + public class DynamicObject extends CitrusSprite { + + public function DynamicObject(name:String, params:Object = null) { + + super(name, params); + } + } +} diff --git a/src/citrus/objects/platformer/simple/Hero.as b/src/citrus/objects/platformer/simple/Hero.as new file mode 100644 index 00000000..ca53d074 --- /dev/null +++ b/src/citrus/objects/platformer/simple/Hero.as @@ -0,0 +1,52 @@ +package citrus.objects.platformer.simple { + + public class Hero extends DynamicObject { + + public var gravity:Number = 50; + + public var acceleration:Number = 10; + public var maxVelocity:Number = 80; + + /** + * Defines which input Channel to listen to. + */ + [Inspectable(defaultValue = "0")] + public var inputChannel:uint = 0; + + public function Hero(name:String, params:Object = null) { + + updateCallEnabled = true; + + super(name, params); + } + + override public function initialize(poolObjectParams:Object = null):void { + + super.initialize(poolObjectParams); + + _velocity.y = gravity; + } + + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + + var moveKeyPressed:Boolean = false; + + if (_ce.input.isDoing("left",inputChannel)) { + _velocity.x -= acceleration; + moveKeyPressed = true; + } + + if (_ce.input.isDoing("right",inputChannel)) { + _velocity.x += acceleration; + moveKeyPressed = true; + } + + if (_velocity.x > (maxVelocity)) + _velocity.x = maxVelocity; + else if (_velocity.x < (-maxVelocity)) + _velocity.x = -maxVelocity; + } + } +} diff --git a/src/citrus/objects/platformer/simple/Sensor.as b/src/citrus/objects/platformer/simple/Sensor.as new file mode 100644 index 00000000..d293a386 --- /dev/null +++ b/src/citrus/objects/platformer/simple/Sensor.as @@ -0,0 +1,9 @@ +package citrus.objects.platformer.simple { + + public class Sensor extends DynamicObject { + + public function Sensor(name:String, params:Object = null) { + super(name, params); + } + } +} diff --git a/src/citrus/objects/platformer/simple/StaticObject.as b/src/citrus/objects/platformer/simple/StaticObject.as new file mode 100644 index 00000000..7c15dda9 --- /dev/null +++ b/src/citrus/objects/platformer/simple/StaticObject.as @@ -0,0 +1,14 @@ +package citrus.objects.platformer.simple { + + import citrus.objects.CitrusSprite; + + /** + * An object that does not move (probably your platform or wall). + */ + public class StaticObject extends CitrusSprite { + + public function StaticObject(name:String, params:Object = null) { + super(name, params); + } + } +} diff --git a/src/citrus/objects/vehicle/nape/Car.as b/src/citrus/objects/vehicle/nape/Car.as new file mode 100644 index 00000000..80babe96 --- /dev/null +++ b/src/citrus/objects/vehicle/nape/Car.as @@ -0,0 +1,290 @@ +package citrus.objects.vehicle.nape { + + import citrus.objects.CitrusSprite; + import citrus.objects.NapePhysicsObject; + + import nape.constraint.DistanceJoint; + import nape.constraint.LineJoint; + import nape.constraint.MotorJoint; + import nape.constraint.WeldJoint; + import nape.geom.Vec2; + import nape.phys.Material; + import nape.shape.Polygon; + + import flash.geom.Point; + + /** + * This car class is a perfect example to show how to combine several physics objects and make a complex object with the Citrus Engine. + * We advice to make it running with the Hills class to create an endless driver game. + * It has a chassis (this class) which will create two weels, add a driver, some nuggets (objects to save) and you can even add some particles to make an exhaust pipe! + * Everything is overrideable to make it fully customizable. Note it may run on Starling or the display list. + * + * Thanks studio3wg/ to let us share this package with the world :) + */ + public class Car extends NapePhysicsObject { + + /** + * Determines if the car has 4WD or just the front wheel. + */ + public var isSuv:Boolean = true; + + public var material:Material = new Material(0, 2, 2, 3, 0.01); + public var driverMaterial:Material = new Material(0, 2, 2, 2.2, 0.01); + public var wheelsMaterial:Material = new Material(0.15,1, 2,3, 2); + + public var motorSpeed:Number = 0; + public var motorAccel:Number = 0.05; + public var maxSpeed:Number = 14; + public var angularVel:Number = 0.5; + + /** + * Like in a Canabalt game, the vehicle max speed will always increase. + */ + public var constantAccel:Number = 0.001; + + public var raceDamper:Number = 30; + public var heightDamper:Number = 30; + public var distanceChassisPivot:Number = 20; + + public var posBackWheel:Number = -37; + public var posFrontWheel:Number = 37; + public var posDriver:Point = new Point(30, 30); + public var damper:Number = 0.28; + public var frequency:Number = 1; + + public var backWheelArt:*; + public var frontWheelArt:*; + public var wheelsGroup:uint = 1; + public var wheelsRadius:Number = 20; + + public var particleArt:*; + protected var _particle:CitrusSprite; + + public var nmbrNuggets:uint = 0; + protected var _nuggets:Vector.; + + protected var _backWheel:Wheel; + protected var _frontWheel:Wheel; + protected var _driver:Driver; + + protected var _lineJoint1:LineJoint; + protected var _lineJoint2:LineJoint; + protected var _distanceJoint1:DistanceJoint; + protected var _distanceJoint2:DistanceJoint; + protected var _motorJoint1:MotorJoint; + protected var _motorJoint2:MotorJoint; + + protected var _launched:Boolean = true; + + public function Car(name:String, params:Object = null) { + + updateCallEnabled = true; + + super(name, params); + } + + override public function addPhysics():void { + super.addPhysics(); + + _addDriver(); + _addWheels(); + _linkCarToWheels(); + _addMotors(); + _addJointsToSpace(); + _addParticle(); + _addNuggets(); + } + + override public function destroy():void { + + _ce.state.remove(_driver); + _ce.state.remove(_frontWheel); + _ce.state.remove(_backWheel); + + if (_particle) + _ce.state.remove(_particle); + + if (_nuggets) { + + for each (var nugget:Nugget in _nuggets) + _ce.state.remove(nugget); + + _nuggets.length = 0; + } + + super.destroy(); + } + + protected function _addDriver():void { + + _driver = new Driver("driver", {material:driverMaterial}); + _ce.state.add(_driver); + + var driverJoint:WeldJoint = new WeldJoint(_body, _driver.body, new Vec2(posDriver.x, -posDriver.y), new Vec2(0, 0)); + driverJoint.space = _nape.space; + } + + protected function _addWheels():void { + + _frontWheel = new Wheel("front wheel", {view:frontWheelArt, group:wheelsGroup, material:wheelsMaterial, radius:wheelsRadius, x:x + posFrontWheel, y:y + distanceChassisPivot}); + _ce.state.add(_frontWheel); + + _lineJoint1 = new LineJoint(_body, _frontWheel.body, new Vec2(posFrontWheel, distanceChassisPivot), new Vec2(0, 0), new Vec2(0, 1), distanceChassisPivot, distanceChassisPivot + heightDamper); + _lineJoint1.ignore = true; + + _backWheel = new Wheel("back wheel", {view:backWheelArt, group:wheelsGroup, material:wheelsMaterial, radius:wheelsRadius, x:x + posBackWheel, y:y + distanceChassisPivot}); + _ce.state.add(_backWheel); + + _lineJoint2 = new LineJoint(_body, _backWheel.body, new Vec2(posBackWheel, distanceChassisPivot), new Vec2(0, 0), new Vec2(0, 1), distanceChassisPivot, distanceChassisPivot + heightDamper); + _lineJoint2.ignore = true; + } + + protected function _linkCarToWheels():void { + + _distanceJoint1 = new DistanceJoint(_body, _frontWheel.body, new Vec2(posFrontWheel, distanceChassisPivot), new Vec2(), distanceChassisPivot + heightDamper, distanceChassisPivot + raceDamper); + _distanceJoint1.stiff = false; + _distanceJoint1.frequency = frequency; + _distanceJoint1.damping = damper; + + _distanceJoint2 = new DistanceJoint(_body, _backWheel.body, new Vec2(posBackWheel, distanceChassisPivot), new Vec2(), distanceChassisPivot + heightDamper, distanceChassisPivot + raceDamper); + _distanceJoint2.stiff = false; + _distanceJoint2.frequency = frequency; + _distanceJoint2.damping = damper; + } + + protected function _addMotors():void { + + _motorJoint1 = new MotorJoint(_nape.space.world, _backWheel.body, 0); + _motorJoint2 = new MotorJoint(_nape.space.world, _frontWheel.body, 0); + } + + protected function _addJointsToSpace():void { + + _lineJoint1.space = _nape.space; + _lineJoint2.space = _nape.space; + _distanceJoint1.space = _nape.space; + _distanceJoint2.space = _nape.space; + _motorJoint1.space = _nape.space; + _motorJoint2.space = _nape.space; + + _body.rotate(new Vec2(0, 0), (0.55 * Math.PI / 180)); + } + + protected function _addParticle():void { + + if (particleArt) { + _particle = new CitrusSprite("particle", {view:particleArt}); + _ce.state.add(_particle); + } + } + + protected function _addNuggets():void { + + if (nmbrNuggets > 0) { + + _nuggets = new Vector.(); + var nugget:Nugget; + for (var i:uint = 0; i < nmbrNuggets; ++i) { + + nugget = new Nugget("nugget" + i, {x:_x, y:_y - _body.bounds.height}); + _nuggets.push(nugget); + _ce.state.add(nugget); + } + } + } + + override protected function createMaterial():void { + _material = material; + } + + override protected function createShape():void { + + var _tab:Array = []; + var vertices:Array = []; + + vertices.push(Vec2.weak(39, 82)); + vertices.push(Vec2.weak(20, 82)); + vertices.push(Vec2.weak(35, 149)); + vertices.push(Vec2.weak(48, 135)); + + _tab.push(vertices); + vertices = []; + + vertices.push(Vec2.weak(134, 83)); + vertices.push(Vec2.weak(122, 135)); + vertices.push(Vec2.weak(133, 148)); + vertices.push(Vec2.weak(150, 83)); + + _tab.push(vertices); + vertices = []; + + vertices.push(Vec2.weak(35, 149)); + vertices.push(Vec2.weak(133, 148)); + vertices.push(Vec2.weak(122, 135)); + vertices.push(Vec2.weak(48, 135)); + + _tab.push(vertices); + + for (var i:uint = 0; i < _tab.length; ++i) { + + var polygonShape:Polygon = new Polygon(_tab[i]); + _shape = polygonShape; + _body.shapes.add(_shape); + + } + + _body.translateShapes(Vec2.weak(-90, -90)); + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + if (launched) { + + motorSpeed += motorAccel; + maxSpeed += constantAccel; + + if (motorSpeed > maxSpeed) + motorSpeed = maxSpeed; + + if (isSuv) + _motorJoint1.rate = _motorJoint2.rate = motorSpeed; + else + _motorJoint2.rate = motorSpeed; + + if (_ce.input.isDoing("left")) + _body.angularVel = -angularVel; + + if (_ce.input.isDoing("right")) + _body.angularVel = angularVel; + } + + if (_particle) { + + _particle.x = x - width; + _particle.y = y + height; + } + + } + + public function get launched():Boolean { + return _launched; + } + + public function set launched(value:Boolean):void { + _launched = value; + + if (_launched) + _motorJoint1.active = _motorJoint2.active = true; + else { + _motorJoint1.active = _motorJoint2.active = false; + motorSpeed = 0; + } + } + + public function get nuggets():Vector. { + return _nuggets; + } + } +} diff --git a/src/citrus/objects/vehicle/nape/Driver.as b/src/citrus/objects/vehicle/nape/Driver.as new file mode 100644 index 00000000..246305f4 --- /dev/null +++ b/src/citrus/objects/vehicle/nape/Driver.as @@ -0,0 +1,55 @@ +package citrus.objects.vehicle.nape { + + import citrus.objects.NapePhysicsObject; + import citrus.objects.platformer.nape.Hills; + import citrus.physics.nape.NapeUtils; + + import nape.callbacks.InteractionCallback; + import nape.phys.Material; + + import org.osflash.signals.Signal; + + /** + * Normally, in a car there is a driver right? This guy will prevent nuggets to fall (take a look on the physics debug view) + * as well as helping you to detect if your car crashed. + */ + public class Driver extends NapePhysicsObject { + + /** + * If the driver touches the ground, this Signal is dispatched. Often it means that you crashed. + */ + public var onGroundTouched:Signal; + + public var material:Material = new Material(0, 2, 2, 2.2, 0.01); + + public function Driver(name:String, params:Object = null) { + + _beginContactCallEnabled = true; + + super(name, params); + + onGroundTouched = new Signal(); + } + + override public function destroy():void { + + onGroundTouched.removeAll(); + + super.destroy(); + } + + override protected function createMaterial():void { + + _material = material; + } + + override public function handleBeginContact(callback:InteractionCallback):void { + + super.handleBeginContact(callback); + + if (NapeUtils.CollisionGetOther(this, callback) is Hills) + onGroundTouched.dispatch(); + } + + } +} diff --git a/src/citrus/objects/vehicle/nape/Nugget.as b/src/citrus/objects/vehicle/nape/Nugget.as new file mode 100644 index 00000000..4469093a --- /dev/null +++ b/src/citrus/objects/vehicle/nape/Nugget.as @@ -0,0 +1,84 @@ +package citrus.objects.vehicle.nape { + + import citrus.objects.NapePhysicsObject; + import citrus.objects.platformer.nape.Hills; + import citrus.physics.nape.NapeUtils; + + import nape.callbacks.InteractionCallback; + import nape.phys.Material; + import nape.shape.Polygon; + + import org.osflash.signals.Signal; + + /** + * In some games, like Snuggle Truck, you carry some objects in your car and you've to reach + * the end of the race with many of them. That's what a nugget is, a whatever item you want that you ahve to save! + */ + public class Nugget extends NapePhysicsObject { + + /** + * Dispatches when a nugget falls from the car. + */ + public var onNuggetLost:Signal; + + protected var _driver:Driver; + protected var _lost:Boolean = false; + + public function Nugget(name:String, params:Object = null) { + + _beginContactCallEnabled = true; + updateCallEnabled = true; + + super(name, params); + + onNuggetLost = new Signal(Nugget); + } + + override public function destroy():void { + + onNuggetLost.removeAll(); + + super.destroy(); + } + + override protected function createMaterial():void { + + _material = new Material(0.0, 0.2, 0.3, 4, 0.01); + } + + override protected function createShape():void { + + _shape = new Polygon(Polygon.rect(0, 0, 12, 12), _material); + _body.shapes.add(_shape); + _body.align(); + } + + override public function handleBeginContact(callback:InteractionCallback):void { + + super.handleBeginContact(callback); + + if (!_lost && NapeUtils.CollisionGetOther(this, callback) is Hills) { + + _lost = true; + + onNuggetLost.dispatch(this); + } + } + + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + + if (!_driver) + _driver = _ce.state.getFirstObjectByType(Driver) as Driver; + + if (_lost && _driver.x > x + _ce.stage.stageWidth) + kill = true; + } + + public function get lost():Boolean { + return _lost; + } + } + +} \ No newline at end of file diff --git a/src/citrus/objects/vehicle/nape/Wheel.as b/src/citrus/objects/vehicle/nape/Wheel.as new file mode 100644 index 00000000..52e7cace --- /dev/null +++ b/src/citrus/objects/vehicle/nape/Wheel.as @@ -0,0 +1,23 @@ +package citrus.objects.vehicle.nape { + + import citrus.objects.NapePhysicsObject; + + import nape.phys.Material; + + /** + * A wheel of the car. You may change its material. + */ + public class Wheel extends NapePhysicsObject { + + public var material:Material = new Material(0.15, 1, 2, 3, 2); + + public function Wheel(name:String, params:Object = null) { + super(name, params); + } + + override protected function createMaterial():void { + + _material = material; + } + } +} diff --git a/src/citrus/physics/APhysicsEngine.as b/src/citrus/physics/APhysicsEngine.as new file mode 100644 index 00000000..8378beaa --- /dev/null +++ b/src/citrus/physics/APhysicsEngine.as @@ -0,0 +1,184 @@ +package citrus.physics { + + import citrus.core.CitrusObject; + import citrus.view.ICitrusArt; + + /** + * An abstract template used by every physics engine. + */ + public class APhysicsEngine extends CitrusObject { + + protected var _visible:Boolean = false; + protected var _touchable:Boolean = false; + protected var _group:uint = 1; + protected var _view:*; + protected var _realDebugView:*; + + protected var _art:ICitrusArt; + + public function APhysicsEngine(name:String, params:Object = null) { + + updateCallEnabled = true; + + super(name, params); + } + + public function getBody():* { + return null; + } + + /** + * Shortcut to the debugView + * use to change the debug drawer's flags with debugView.debugMode() + * or access it directly through debugView.debugDrawer. + * + * exists only after the physics engine has been added to the state. + * + * Example : changing the debug views flags: + * + * Box2D : + * + * var b2d:Box2D = new Box2D("b2d"); + * b2d.gravity = b2Vec2.Make(0,0); + * b2d.visible = true; + * add(b2d); + * + * b2d.debugView.debugMode(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit); + * //or + * (b2d.debugView.debugDrawer as b2DebugDraw).SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit); + * + * + * Nape: + * + * nape = new Nape("nape"); + * nape.visible = true; + * add(nape); + * + * nape.debugView.debugMode(NapeDebugArt.draw_Bodies | NapeDebugArt.draw_BodyDetail | NapeDebugArt.draw_CollisionArbiters); + * //or + * var shapedebug:ShapeDebug = nape.debugView.debugDrawer as ShapeDebug; + * shapedebug.drawBodies = true; + * shapedebug.drawBodyDetail = true; + * shapedebug.drawCollisionArbiters = true; + * + */ + public function get debugView():IDebugView { + var debugArt:* = _ce.state.view.getArt(this); + if(debugArt && debugArt.content) + return debugArt.content.debugView as IDebugView; + else + return null; + } + + public function get realDebugView():* { + return _realDebugView; + } + + public function get view():* { + return _view; + } + + public function set view(value:*):void { + _view = value; + } + + /** + * @inheritDoc + */ + public function get art():ICitrusArt + { + return _art; + } + + public function get x():Number { + return 0; + } + + public function get y():Number { + return 0; + } + + public function get z():Number { + return 0; + } + + public function get width():Number { + return 0; + } + + public function get height():Number { + return 0; + } + + public function get depth():Number { + return 0; + } + + public function get velocity():Array { + return null; + } + + public function get parallaxX():Number { + return 1; + } + + public function get parallaxY():Number { + return 1; + } + + public function get rotation():Number { + return 0; + } + + public function get group():uint { + return _group; + } + + public function set group(value:uint):void { + _group = value; + } + + public function get visible():Boolean { + return _visible; + } + + public function set visible(value:Boolean):void { + _visible = value; + } + + public function get touchable():Boolean { + return _touchable; + } + + public function set touchable(value:Boolean):void { + _touchable = value; + } + + public function get animation():String { + return ""; + } + + public function get inverted():Boolean { + return false; + } + + public function get offsetX():Number { + return 0; + } + + public function get offsetY():Number { + return 0; + } + + public function get registration():String { + return "topLeft"; + } + + public function handleArtReady(citrusArt:ICitrusArt):void { + _art = citrusArt; + } + + public function handleArtChanged(citrusArt:ICitrusArt):void { + } + } +} diff --git a/src/citrus/physics/IDebugView.as b/src/citrus/physics/IDebugView.as new file mode 100644 index 00000000..fbb256c8 --- /dev/null +++ b/src/citrus/physics/IDebugView.as @@ -0,0 +1,35 @@ +package citrus.physics { + + import flash.geom.Matrix; + + /** + * Interface for all the debug views + */ + + public interface IDebugView { + + /** + * update the debug view + */ + function update():void + + /** + * change the debug mode when available, e.g. show only joints, or raycasts... + */ + function debugMode(flags:uint):void + + function initialize():void + function destroy():void + + function set transformMatrix(m:Matrix):void + function get transformMatrix():Matrix + + function set visibility(val:Boolean):void + function get visibility():Boolean + + /** + * returns the b2DebugDraw for Box2D, ShapeDebug for Nape... + */ + function get debugDrawer():* + } +} diff --git a/src/citrus/physics/PhysicsCollisionCategories.as b/src/citrus/physics/PhysicsCollisionCategories.as new file mode 100644 index 00000000..78a053f5 --- /dev/null +++ b/src/citrus/physics/PhysicsCollisionCategories.as @@ -0,0 +1,112 @@ +package citrus.physics +{ + /** + * Physics Engine uses bits to represent collision categories. + * + *

    If you don't understand binary and bit shifting, then it may get kind of confusing trying to work + * with physics engine categories, so I've created this class that those bits can be accessed by + * creating and referring to String representations.

    + * + *

    The bit system is actually really great because any combination of categories can actually be + * represented by a single integer value. However, using bitwise operations is not always readable + * for everyone, so this call is meant to be as light of a wrapper as possible for managing collision + * categories with the Citrus Engine.

    + * + *

    The constructors of the Physics Engine classes create a couple of initial categories for you to use: + * GoodGuys, BadGuys, Items, Level. If you need more, you can always add more categories, but don't complicate + * it just for the sake of adding fun category names. The categories created by the Physics Engine classes are used by the + * platformer kit that comes with Citrus Engine.

    + */ + public class PhysicsCollisionCategories + { + private static var _allCategories:uint = 0; + private static var _numCategories:uint = 0; + private static var _categoryIndexes:Array = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384]; + private static var _categoryNames:Object = {}; + + /** + * Returns true if the categories in the first parameter contain the category(s) in the second parameter. + * @param categories The categories to check against. + * @param theCategory The category you want to know exists in the categories of the first parameter. + */ + public static function Has(categories:uint, theCategory:uint):Boolean + { + return Boolean(categories & theCategory); + } + + /** + * Add a category to the collision categories list. + * @param categoryName The name of the category. + */ + public static function Add(categoryName:String):void + { + if (_categoryNames[categoryName]) + return; + + if (_numCategories == 15) + throw new Error("You can only have 15 categories."); + + _categoryNames[categoryName] = _categoryIndexes[_numCategories]; + _allCategories |= _categoryIndexes[_numCategories]; + _numCategories++; + } + + /** + * Gets the category(s) integer by name. You can pass in multiple category names, and it will return the appropriate integer. + * @param ...args The categories that you want the integer for. + * @return A single integer representing the category(s) you passed in. + */ + public static function Get(...args):uint + { + var categories:uint = 0; + for each (var name:String in args) + { + var category:uint = _categoryNames[name]; + if (category == 0) + { + trace("Warning: " + name + " category does not exist."); + continue; + } + categories |= _categoryNames[name]; + } + return categories; + } + + /** + * Returns an integer representing all categories. + */ + public static function GetAll():uint + { + return _allCategories; + } + + /** + * Returns an integer representing all categories except the ones whose names you pass in. + * @param ...args The names of the categories you want excluded from the result. + */ + public static function GetAllExcept(...args):uint + { + var categories:uint = _allCategories; + for each (var name:String in args) + { + var category:uint = _categoryNames[name]; + if (category == 0) + { + trace("Warning: " + name + " category does not exist."); + continue; + } + categories &= (~_categoryNames[name]); + } + return categories; + } + + /** + * Returns the number zero, which means no categories. You can also just use the number zero instead of this function (but this reads better). + */ + public static function GetNone():uint + { + return 0; + } + } + +} diff --git a/src/citrus/physics/box2d/Box2D.as b/src/citrus/physics/box2d/Box2D.as new file mode 100644 index 00000000..b8ac15ff --- /dev/null +++ b/src/citrus/physics/box2d/Box2D.as @@ -0,0 +1,116 @@ +package citrus.physics.box2d { + + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.b2World; + + import citrus.physics.APhysicsEngine; + import citrus.physics.PhysicsCollisionCategories; + import citrus.view.ISpriteView; + + /** + * This is a simple wrapper class that allows you to add a Box2D world to your game's state. + * Add an instance of this class to your State before you create any physics bodies. It will need to + * exist first, or your physics bodies will throw an error when they try to create themselves. + */ + public class Box2D extends APhysicsEngine implements ISpriteView + { + /** + * timeStep the amount of time to simulate, this should not vary. + */ + public var timeStep:Number = 1 / 20; + + /** + * velocityIterations for the velocity constraint solver. + */ + public var velocityIterations:uint = 8; + + /** + *positionIterations for the position constraint solver. + */ + public var positionIterations:uint = 8; + + private var _scale:Number = 30; + private var _world:b2World; + private var _gravity:b2Vec2 = new b2Vec2(0, 15); + private var _contactListener:Box2DContactListener; + + /** + * Creates and initializes a Box2D world. + */ + public function Box2D(name:String, params:Object = null) + { + if (params && params.view == undefined) + params.view = Box2DDebugArt; + else if (params == null) + params = {view:Box2DDebugArt}; + + super(name, params); + } + + override public function initialize(poolObjectParams:Object = null):void { + + super.initialize(); + + _realDebugView = _view; + + _world = new b2World(_gravity, true); + _contactListener = new Box2DContactListener(); + _world.SetContactListener(_contactListener); + + //Set up collision categories + PhysicsCollisionCategories.Add("GoodGuys"); + PhysicsCollisionCategories.Add("BadGuys"); + PhysicsCollisionCategories.Add("Level"); + PhysicsCollisionCategories.Add("Items"); + } + + override public function destroy():void + { + super.destroy(); + } + + /** + * Gets a reference to the actual Box2D world object. + */ + public function get world():b2World + { + return _world; + } + + /** + * This is hard to grasp, but Box2D does not use pixels for its physics values. Cutely, it uses meters + * and forces us to convert those meter values to pixels by multiplying by 30. If you don't multiple Box2D + * values by 30, your objects will look very small and will appear to move very slowly, if at all. + * This is a reference to the scale number by which you must multiply your values to properly display physics objects. + */ + public function get scale():Number + { + return _scale; + } + + /** + * Change the gravity of the world. + */ + public function get gravity():b2Vec2 { + return _gravity; + } + + public function set gravity(value:b2Vec2):void { + _gravity = value; + + if (_world) + _world.SetGravity(_gravity); + } + + /** + * This is where the time step of the physics world occurs. + */ + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + _world.Step(timeStep, velocityIterations, positionIterations); + _world.ClearForces(); + } + } +} \ No newline at end of file diff --git a/src/citrus/physics/box2d/Box2DContactListener.as b/src/citrus/physics/box2d/Box2DContactListener.as new file mode 100644 index 00000000..35886306 --- /dev/null +++ b/src/citrus/physics/box2d/Box2DContactListener.as @@ -0,0 +1,95 @@ +package citrus.physics.box2d { + + import Box2D.Collision.b2Manifold; + import Box2D.Collision.b2WorldManifold; + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.b2ContactImpulse; + import Box2D.Dynamics.b2ContactListener; + + /** + * Used to report the contact's interaction between objects. It calls function in Box2dPhysicsObject. + */ + public class Box2DContactListener extends b2ContactListener { + + private var _worldManifold:b2WorldManifold; + + public function Box2DContactListener() { + _worldManifold = new b2WorldManifold(); + } + + override public function BeginContact(contact:b2Contact):void { + + var a:IBox2DPhysicsObject = contact.GetFixtureA().GetBody().GetUserData(); + var b:IBox2DPhysicsObject = contact.GetFixtureB().GetBody().GetUserData(); + + if (!a || !b) + return; + + _contactGetWorldManifoldValues(contact); + + if (a.beginContactCallEnabled) + a.handleBeginContact(contact); + + if (b.beginContactCallEnabled) + b.handleBeginContact(contact); + } + + override public function EndContact(contact:b2Contact):void { + + var a:IBox2DPhysicsObject = contact.GetFixtureA().GetBody().GetUserData(); + var b:IBox2DPhysicsObject = contact.GetFixtureB().GetBody().GetUserData(); + + if (!a || !b) + return; + + _contactGetWorldManifoldValues(contact); + + if (a.endContactCallEnabled) + a.handleEndContact(contact); + + if (b.endContactCallEnabled) + b.handleEndContact(contact); + } + + override public function PreSolve(contact:b2Contact, oldManifold:b2Manifold):void { + + var a:IBox2DPhysicsObject = contact.GetFixtureA().GetBody().GetUserData(); + var b:IBox2DPhysicsObject = contact.GetFixtureB().GetBody().GetUserData(); + + if (!a || !b) + return; + + _contactGetWorldManifoldValues(contact); + + if (a.preContactCallEnabled) + a.handlePreSolve(contact, oldManifold); + + if (b.preContactCallEnabled) + b.handlePreSolve(contact, oldManifold); + } + + override public function PostSolve(contact:b2Contact, impulse:b2ContactImpulse):void { + + var a:IBox2DPhysicsObject = contact.GetFixtureA().GetBody().GetUserData(); + var b:IBox2DPhysicsObject = contact.GetFixtureB().GetBody().GetUserData(); + + if (!a || !b) + return; + + _contactGetWorldManifoldValues(contact); + + if (a.postContactCallEnabled) + a.handlePostSolve(contact, impulse); + + if (b.postContactCallEnabled) + b.handlePostSolve(contact, impulse); + } + + private function _contactGetWorldManifoldValues(contact:b2Contact):void { + contact.GetWorldManifold(_worldManifold); + contact.normal = _worldManifold.m_normal; + contact.contactPoints = _worldManifold.m_points; + } + + } +} diff --git a/src/citrus/physics/box2d/Box2DDebugArt.as b/src/citrus/physics/box2d/Box2DDebugArt.as new file mode 100644 index 00000000..2071a35f --- /dev/null +++ b/src/citrus/physics/box2d/Box2DDebugArt.as @@ -0,0 +1,88 @@ +package citrus.physics.box2d { + + import Box2D.Dynamics.b2DebugDraw; + import citrus.core.CitrusEngine; + import citrus.physics.IDebugView; + import flash.display.Sprite; + import flash.events.Event; + import flash.geom.Matrix; + + /** + * This displays Box2D's debug graphics. It does so properly through Citrus Engine's view manager. Box2D by default + * sets visible to false with an alpha of 0.5, so you'll need to set the Box2D object's visible property to true in order to see the debug graphics. + */ + public class Box2DDebugArt implements IDebugView + { + private var _box2D:Box2D; + private var _debugDrawer:b2DebugDraw; + private var _sprite:Sprite; + private var _ce:CitrusEngine; + + public function Box2DDebugArt() + { + _ce = CitrusEngine.getInstance(); + + _box2D = _ce.state.getFirstObjectByType(Box2D) as Box2D; + + _debugDrawer = new b2DebugDraw(); + + _sprite = new Sprite(); + _sprite.name = "debug view"; + + _debugDrawer.SetSprite(_sprite); + _debugDrawer.SetDrawScale(_box2D.scale); + _debugDrawer.SetFlags(b2DebugDraw.e_shapeBit|b2DebugDraw.e_jointBit); + + _box2D.world.SetDebugDraw(_debugDrawer); + + _sprite.alpha = 0.5; + } + + public function initialize():void + { + _ce.stage.addChild(_sprite); + } + + public function update():void + { + if (_box2D.visible) + _box2D.world.DrawDebugData(); + } + + public function destroy():void + { + _ce.stage.removeChild(_sprite); + _debugDrawer = null; + _box2D = null; + } + + public function debugMode(flags:uint):void { + _debugDrawer.SetFlags(flags); + } + + public function get debugDrawer():* { + return _debugDrawer; + } + + public function set transformMatrix(m:Matrix):void + { + _sprite.transform.matrix = m; + } + + public function get transformMatrix():Matrix + { + return _sprite.transform.matrix; + } + + public function get visibility():Boolean + { + return _sprite.visible; + } + + public function set visibility(val:Boolean):void + { + _sprite.visible = val; + } + + } +} \ No newline at end of file diff --git a/src/citrus/physics/box2d/Box2DShapeMaker.as b/src/citrus/physics/box2d/Box2DShapeMaker.as new file mode 100644 index 00000000..1e66e20e --- /dev/null +++ b/src/citrus/physics/box2d/Box2DShapeMaker.as @@ -0,0 +1,51 @@ +package citrus.physics.box2d +{ + + import Box2D.Collision.Shapes.b2CircleShape; + import Box2D.Collision.Shapes.b2PolygonShape; + import Box2D.Common.Math.b2Vec2; + + /** + * This class helps to define some complex shapes using vertices. + */ + public class Box2DShapeMaker + { + public static function BeveledRect(width:Number, height:Number, bevel:Number):b2PolygonShape + { + var halfWidth:Number = width * 0.5; + var halfHeight:Number = height * 0.5; + + var shape:b2PolygonShape = new b2PolygonShape(); + var vertices:Array = []; + vertices.push(new b2Vec2( -halfWidth + bevel, -halfHeight)); + vertices.push(new b2Vec2( halfWidth - bevel, -halfHeight)); + vertices.push(new b2Vec2( halfWidth, -halfHeight + bevel)); + vertices.push(new b2Vec2( halfWidth, halfHeight - bevel)); + vertices.push(new b2Vec2( halfWidth - bevel, halfHeight)); + vertices.push(new b2Vec2( -halfWidth + bevel, halfHeight)); + vertices.push(new b2Vec2( -halfWidth, halfHeight - bevel)); + vertices.push(new b2Vec2( -halfWidth, -halfHeight + bevel)); + shape.SetAsArray(vertices); + + return shape; + } + + public static function Rect(width:Number, height:Number):b2PolygonShape + { + var shape:b2PolygonShape = new b2PolygonShape(); + shape.SetAsBox(width * 0.5, height * 0.5); + + return shape; + } + + public static function Circle(width:Number, height:Number):b2CircleShape + { + var radius:Number = (width + height) * 0.25; + var shape:b2CircleShape = new b2CircleShape(); + shape.SetRadius(radius); + + return shape; + } + } + +} \ No newline at end of file diff --git a/src/citrus/physics/box2d/Box2DUtils.as b/src/citrus/physics/box2d/Box2DUtils.as new file mode 100644 index 00000000..4f068ab0 --- /dev/null +++ b/src/citrus/physics/box2d/Box2DUtils.as @@ -0,0 +1,68 @@ +package citrus.physics.box2d { + + import Box2D.Common.Math.b2Vec2; + import Box2D.Dynamics.Contacts.b2Contact; + + /** + * This class provides some useful Box2D functions. + */ + public class Box2DUtils + { + /** + * In Box2D we are blind concerning the collision, we are never sure which body is the collider. This function should help. + * Call this function to obtain the colliding physics object. + * @param self in CE's code, we give this. In your code it will be your hero, a sensor, ... + * @param the contact. + * @return the collider. + */ + static public function CollisionGetOther(self:IBox2DPhysicsObject, contact:b2Contact):IBox2DPhysicsObject { + return self == contact.GetFixtureA().GetBody().GetUserData() ? contact.GetFixtureB().GetBody().GetUserData() : contact.GetFixtureA().GetBody().GetUserData(); + } + + /** + * In Box2D we are blind concerning the collision, we are never sure which body is the collider. This function should help. + * Call this function to obtain the collided physics object. + * @param self in CE's code, we give this. In your code it will be your hero, a sensor, ... + * @param the contact. + * @return the collided. + */ + static public function CollisionGetSelf(self:IBox2DPhysicsObject, contact:b2Contact):IBox2DPhysicsObject { + return self == contact.GetFixtureA().GetBody().GetUserData() ? contact.GetFixtureA().GetBody().GetUserData() : contact.GetFixtureB().GetBody().GetUserData(); + } + + /** + * In Box2D we are blind concerning the collision, we are never sure which body is the collider. This function should help. + * Call this function to obtain the object of the type wanted. + * @param objectClass the class whose you want to pick up the object. + * @param callback the InteractionCallback. + * @return the object of the class desired. + */ + static public function CollisionGetObjectByType(objectClass:Class, contact:b2Contact):IBox2DPhysicsObject { + return contact.GetFixtureA().GetBody().GetUserData() is objectClass ? contact.GetFixtureA().GetBody().GetUserData() : contact.GetFixtureB().GetBody().GetUserData(); + } + + /** + * Function to rotate a b2Vec2 vector. + * @param vector the initial vector + * @param angle the angle desired + * @return the rotated b2Vec2 + */ + static public function Rotateb2Vec2(vector:b2Vec2, angle:Number):b2Vec2 { + var cos:Number = Math.cos(angle); + var sin:Number = Math.sin(angle); + return new b2Vec2(vector.x * cos - vector.y * sin, vector.x * sin + vector.y * cos); + } + + /** + * Multiply a b2Vec2 vector by an other. + * @param a the b2Vec2 to be multiplied. + * @param b the b2Vec2 multiplier. + * @return the multiplied vector. + */ + static public function Multiply2(a:b2Vec2, b:b2Vec2):b2Vec2 { + a.x *= b.x; + a.y *= b.y; + return a; + } + } +} \ No newline at end of file diff --git a/src/citrus/physics/box2d/IBox2DPhysicsObject.as b/src/citrus/physics/box2d/IBox2DPhysicsObject.as new file mode 100644 index 00000000..bf443964 --- /dev/null +++ b/src/citrus/physics/box2d/IBox2DPhysicsObject.as @@ -0,0 +1,44 @@ +package citrus.physics.box2d { + + import Box2D.Collision.b2Manifold; + import Box2D.Dynamics.Contacts.b2Contact; + import Box2D.Dynamics.b2Body; + import Box2D.Dynamics.b2ContactImpulse; + + /** + * An interface used by each Box2D object. It helps to enable interaction between entity/component object and "normal" object. + */ + public interface IBox2DPhysicsObject + { + function handleBeginContact(contact:b2Contact):void; + function handleEndContact(contact:b2Contact):void; + function handlePreSolve(contact:b2Contact, oldManifold:b2Manifold):void; + function handlePostSolve(contact:b2Contact, impulse:b2ContactImpulse):void; + function fixedUpdate():void; + function get x():Number; + function set x(value:Number):void; + function get y():Number; + function set y(value:Number):void; + function get z():Number; + function get rotation():Number; + function set rotation(value:Number):void; + function get width():Number; + function set width(value:Number):void; + function get height():Number; + function set height(value:Number):void; + function get depth():Number; + function get radius():Number; + function set radius(value:Number):void; + function get body():b2Body; + function getBody():*; + + function get beginContactCallEnabled():Boolean; + function set beginContactCallEnabled(value:Boolean):void; + function get endContactCallEnabled():Boolean; + function set endContactCallEnabled(value:Boolean):void; + function get preContactCallEnabled():Boolean; + function set preContactCallEnabled(value:Boolean):void; + function get postContactCallEnabled():Boolean; + function set postContactCallEnabled(value:Boolean):void; + } +} \ No newline at end of file diff --git a/src/citrus/physics/nape/INapePhysicsObject.as b/src/citrus/physics/nape/INapePhysicsObject.as new file mode 100644 index 00000000..4c2e491a --- /dev/null +++ b/src/citrus/physics/nape/INapePhysicsObject.as @@ -0,0 +1,39 @@ +package citrus.physics.nape { + + import nape.callbacks.InteractionCallback; + import nape.callbacks.PreCallback; + import nape.callbacks.PreFlag; + import nape.phys.Body; + + /** + * An interface used by each Nape object. It helps to enable interaction between entity/component object and "normal" object. + */ + public interface INapePhysicsObject { + + function handleBeginContact(callback:InteractionCallback):void; + function handleEndContact(callback:InteractionCallback):void; + function handlePreContact(callback:PreCallback):PreFlag; + function fixedUpdate():void; + function get x():Number; + function set x(value:Number):void; + function get y():Number; + function set y(value:Number):void; + function get z():Number; + function get rotation():Number; + function set rotation(value:Number):void; + function get width():Number; + function set width(value:Number):void; + function get height():Number; + function set height(value:Number):void; + function get depth():Number; + function get radius():Number; + function set radius(value:Number):void; + function get body():Body; + function getBody():*; + + function get beginContactCallEnabled():Boolean; + function set beginContactCallEnabled(value:Boolean):void; + function get endContactCallEnabled():Boolean; + function set endContactCallEnabled(value:Boolean):void; + } +} diff --git a/src/citrus/physics/nape/Nape.as b/src/citrus/physics/nape/Nape.as new file mode 100644 index 00000000..46115359 --- /dev/null +++ b/src/citrus/physics/nape/Nape.as @@ -0,0 +1,113 @@ +package citrus.physics.nape { + + import citrus.physics.APhysicsEngine; + import citrus.physics.PhysicsCollisionCategories; + import citrus.view.ISpriteView; + + import nape.geom.Vec2; + import nape.space.Space; + + /** + * This is a simple wrapper class that allows you to add a Nape space to your game's state. + * Add an instance of this class to your State before you create any physics bodies. It will need to + * exist first, or your physics bodies will throw an error when they try to create themselves. + */ + public class Nape extends APhysicsEngine implements ISpriteView { + + /** + * timeStep the amount of time to simulate, this should not vary. + */ + public var timeStep:Number = 1 / 20; + + /** + * velocityIterations for the velocity constraint solver. + */ + public var velocityIterations:uint = 8; + + /** + *positionIterations for the position constraint solver. + */ + public var positionIterations:uint = 8; + + private var _space:Space; + private var _gravity:Vec2 = new Vec2(0, 450); + private var _contactListener:NapeContactListener; + + /** + * Creates and initializes a Nape space. + */ + public function Nape(name:String, params:Object = null) { + + if (params && params.view == undefined) + params.view = NapeDebugArt; + else if (params == null) + params = {view:NapeDebugArt}; + + super(name, params); + } + + override public function initialize(poolObjectParams:Object = null):void { + + super.initialize(); + + _realDebugView = _view; + + _space = new Space(_gravity); + _contactListener = new NapeContactListener(_space); + + //Set up collision categories + PhysicsCollisionCategories.Add("GoodGuys"); + PhysicsCollisionCategories.Add("BadGuys"); + PhysicsCollisionCategories.Add("Level"); + PhysicsCollisionCategories.Add("Items"); + } + + override public function destroy():void { + + _contactListener.destroy(); + _contactListener = null; + _space.clear(); + _space = null; + _gravity.dispose(); + super.destroy(); + } + + /** + * Gets a reference to the actual Nape space object. + */ + public function get space():Space { + return _space; + } + + /** + * Change the gravity of the space. + */ + public function get gravity():Vec2 { + return _gravity; + } + + public function set gravity(value:Vec2):void { + _gravity = value; + + if (_space) + _space.gravity = _gravity; + } + + /** + * Return a ContactListener class where some InteractionListeners are already defined. + */ + public function get contactListener():NapeContactListener { + return _contactListener; + } + + /** + * This is where the time step of the physics world occurs. + */ + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + + _space.step(timeStep, velocityIterations, positionIterations); + } + } +} diff --git a/src/citrus/physics/nape/NapeContactListener.as b/src/citrus/physics/nape/NapeContactListener.as new file mode 100644 index 00000000..9bedd169 --- /dev/null +++ b/src/citrus/physics/nape/NapeContactListener.as @@ -0,0 +1,86 @@ +package citrus.physics.nape { + + import nape.callbacks.CbEvent; + import nape.callbacks.CbType; + import nape.callbacks.InteractionCallback; + import nape.callbacks.InteractionListener; + import nape.callbacks.InteractionType; + import nape.space.Space; + + /** + * Used to determine the contact's interaction between objects. It calls function in NapePhysicsObject. + */ + public class NapeContactListener { + + private var _space:Space; + private var _enabled:Boolean = false; + + private var _beginInteractionListener:InteractionListener; + private var _endInteractionListener:InteractionListener; + + public function NapeContactListener(space:Space) { + + _space = space; + + _beginInteractionListener = new InteractionListener(CbEvent.BEGIN, InteractionType.ANY, CbType.ANY_BODY, CbType.ANY_BODY, onInteractionBegin); + _endInteractionListener = new InteractionListener(CbEvent.END, InteractionType.ANY, CbType.ANY_BODY, CbType.ANY_BODY, onInteractionEnd); + + enabled = true; + } + + public function destroy():void { + + _space.listeners.clear(); + } + + public function onInteractionBegin(interactionCallback:InteractionCallback):void { + + var a:INapePhysicsObject = interactionCallback.int1.userData.myData; + var b:INapePhysicsObject = interactionCallback.int2.userData.myData; + + if (!a || !b) + return; + + if (a.beginContactCallEnabled) + a.handleBeginContact(interactionCallback); + + if (b.beginContactCallEnabled) + b.handleBeginContact(interactionCallback); + } + + public function onInteractionEnd(interactionCallback:InteractionCallback):void { + + var a:INapePhysicsObject = interactionCallback.int1.userData.myData; + var b:INapePhysicsObject = interactionCallback.int2.userData.myData; + + if (!a || !b) + return; + + if (a.endContactCallEnabled) + a.handleEndContact(interactionCallback); + + if (b.endContactCallEnabled) + b.handleEndContact(interactionCallback); + } + + public function set enabled(value:Boolean):void { + + if (_enabled == value) + return; + + _enabled = value; + + if(_enabled) { + _space.listeners.add(_beginInteractionListener); + _space.listeners.add(_endInteractionListener); + } else { + _space.listeners.remove(_beginInteractionListener); + _space.listeners.remove(_endInteractionListener); + } + } + + public function get enabled():Boolean { + return _enabled; + } + } +} diff --git a/src/citrus/physics/nape/NapeDebugArt.as b/src/citrus/physics/nape/NapeDebugArt.as new file mode 100644 index 00000000..9afb3957 --- /dev/null +++ b/src/citrus/physics/nape/NapeDebugArt.as @@ -0,0 +1,147 @@ +package citrus.physics.nape { + + import citrus.core.CitrusEngine; + import citrus.datastructures.BitFlag; + import citrus.physics.IDebugView; + import flash.display.Sprite; + import flash.events.Event; + import flash.geom.Matrix; + import nape.util.ShapeDebug; + + /** + * This displays Nape's debug graphics. It does so properly through Citrus Engine's view manager. Nape by default + * sets visible to false with an alpha of 0.8, so you'll need to set the Nape object's visible property to true in order to see the debug graphics. + */ + public class NapeDebugArt implements IDebugView { + + private var _nape:Nape; + private var _debugDrawer:ShapeDebug; + private var _ce:CitrusEngine; + + /** + * NapDebugArt flags. + * after modifying them, call applyFlags() to set the ShapeDebug's boolean values. + */ + public var flags:BitFlag; + + public static const draw_Bodies:uint = 1 << 0; + public static const draw_BodyDetail:uint = 1 << 1; + public static const draw_CollisionArbiters:uint = 1 << 2; + public static const draw_Constraints:uint = 1 << 3; + public static const draw_FluidArbiters:uint = 1 << 4; + public static const draw_SensorArbiters:uint = 1 << 5; + public static const draw_ShapeAngleIndicators:uint = 1 << 6; + public static const draw_ShapeDetail:uint = 1 << 7; + + public function NapeDebugArt() { + + _ce = CitrusEngine.getInstance(); + + flags = new BitFlag(NapeDebugArt); + + _nape = _ce.state.getFirstObjectByType(Nape) as Nape; + + _debugDrawer = new ShapeDebug(_ce.screenWidth, _ce.screenHeight); + + _debugDrawer.display.name = "debug view"; + _debugDrawer.display.alpha = 0.8; + + readFlags(); + + _ce.onStageResize.add(resize); + } + + protected function applyFlags():void + { + _debugDrawer.drawBodies = flags.hasFlag(draw_Bodies); + _debugDrawer.drawBodyDetail = flags.hasFlag(draw_BodyDetail); + _debugDrawer.drawCollisionArbiters = flags.hasFlag(draw_BodyDetail); + _debugDrawer.drawConstraints = flags.hasFlag(draw_Constraints); + _debugDrawer.drawFluidArbiters = flags.hasFlag(draw_FluidArbiters); + _debugDrawer.drawSensorArbiters = flags.hasFlag(draw_SensorArbiters); + _debugDrawer.drawShapeAngleIndicators = flags.hasFlag(draw_ShapeAngleIndicators); + _debugDrawer.drawShapeDetail = flags.hasFlag(draw_ShapeDetail); + } + + protected function readFlags():void + { + flags.removeAllFlags(); + if(_debugDrawer.drawBodies) flags.addFlag(draw_Bodies); + if(_debugDrawer.drawBodyDetail) flags.addFlag(draw_BodyDetail); + if(_debugDrawer.drawCollisionArbiters) flags.addFlag(draw_BodyDetail); + if(_debugDrawer.drawConstraints) flags.addFlag(draw_Constraints); + if(_debugDrawer.drawFluidArbiters) flags.addFlag(draw_FluidArbiters); + if(_debugDrawer.drawSensorArbiters) flags.addFlag(draw_SensorArbiters); + if(_debugDrawer.drawShapeAngleIndicators) flags.addFlag(draw_ShapeAngleIndicators); + if(_debugDrawer.drawShapeDetail) flags.addFlag(draw_ShapeDetail); + } + + public function initialize():void + { + _ce.stage.addChild(_debugDrawer.display); + } + + public function resize(w:Number, h:Number):void + { + if (!_nape.visible) + return; + + readFlags(); + _ce.stage.removeChild(_debugDrawer.display); + _debugDrawer.flush(); + _debugDrawer = new ShapeDebug(_ce.screenWidth, _ce.screenHeight); + _debugDrawer.display.name = "debug view"; + _debugDrawer.display.alpha = 0.8; + applyFlags(); + _ce.stage.addChild(_debugDrawer.display); + } + + public function update():void + { + if (_nape.visible) { + + _debugDrawer.clear(); + _debugDrawer.draw(_nape.space); + _debugDrawer.flush(); + } + } + + public function destroy():void + { + flags.destroy(); + _ce.onStageResize.remove(resize); + _ce.stage.removeChild(_debugDrawer.display); + _debugDrawer = null; + } + + public function debugMode(flags:uint):void { + this.flags.setFlags(flags); + applyFlags(); + } + + public function get debugDrawer():* { + return _debugDrawer; + } + + public function get transformMatrix():Matrix + { + return _debugDrawer.transform.toMatrix(); + } + + public function set transformMatrix(m:Matrix):void + { + //flash Matrix is Mat23 with b and c swapped + _debugDrawer.transform.setAs(m.a, m.c, m.b, m.d, m.tx, m.ty); + } + + public function get visibility():Boolean + { + return _debugDrawer.display.visible; + } + + public function set visibility(val:Boolean):void + { + _debugDrawer.display.visible = val; + } + } +} diff --git a/src/citrus/physics/nape/NapeUtils.as b/src/citrus/physics/nape/NapeUtils.as new file mode 100644 index 00000000..185cd5e1 --- /dev/null +++ b/src/citrus/physics/nape/NapeUtils.as @@ -0,0 +1,108 @@ +package citrus.physics.nape { + + import citrus.objects.NapePhysicsObject; + import nape.callbacks.PreCallback; + import nape.dynamics.Arbiter; + import nape.phys.Body; + import nape.phys.Interactor; + import nape.shape.Shape; + + import nape.callbacks.InteractionCallback; + + /** + * This class provides some useful Nape functions. + */ + public class NapeUtils { + + /** + * In Nape we are blind concerning the collision, we are never sure which body is the collider. This function should help. + * Call this function to obtain the colliding physics object. + * @param self in CE's code, we give this. In your code it will be your hero, a sensor, ... + * @param callback the InteractionCallback. + * @return the collider. + */ + static public function CollisionGetOther(self:NapePhysicsObject, callback:InteractionCallback):NapePhysicsObject { + return self == callback.int1.userData.myData ? callback.int2.userData.myData : callback.int1.userData.myData; + } + + /** + * In Nape we are blind concerning the collision, we are never sure which body is the collider. This function should help. + * Call this function to obtain the collided physics object. + * @param self in CE's code, we give this. In your code it will be your hero, a sensor, ... + * @param callback the InteractionCallback. + * @return the collided. + */ + static public function CollisionGetSelf(self:NapePhysicsObject, callback:InteractionCallback):NapePhysicsObject { + return self == callback.int1.userData.myData ? callback.int1.userData.myData : callback.int2.userData.myData; + } + + /** + * Similar to CollisionGetOther but for PreCallbacks. + * @param self in CE's code, we give this. In your code it will be your hero, a sensor, ... + * @param callback the PreCallback. + * @return the collider. + */ + static public function PreCollisionGetOther(self:NapePhysicsObject, callback:PreCallback):NapePhysicsObject { + return self == callback.int1.userData.myData ? callback.int2.userData.myData : callback.int1.userData.myData; + } + + /** + * Similar to CollisionGetSelf but for PreCallbacks. + * @param self in CE's code, we give this. In your code it will be your hero, a sensor, ... + * @param callback the PreCallback. + * @return the collided. + */ + static public function PreCollisionGetSelf(self:NapePhysicsObject, callback:PreCallback):NapePhysicsObject { + return self == callback.int1.userData.myData ? callback.int1.userData.myData : callback.int2.userData.myData; + } + + /** + * get the Interactor object in which self is NOT involved. + * @param self + * @param callback + * @return + */ + static public function getOtherInteractor(self:NapePhysicsObject, callback:InteractionCallback):Interactor { + return self == callback.int1.userData.myData ? callback.int2 : callback.int1; + } + + /** + * get the Interactor object in which self is involved. + * @param self + * @param callback + * @return + */ + static public function getSelfInteractor(self:NapePhysicsObject, callback:InteractionCallback):Interactor { + return self == callback.int1.userData.myData ? callback.int1 : callback.int2; + } + + /** + * Return the shape involved in the a arbiter that is part of body. + * return null if body is not involved in the arbiter or if neither shape belongs to the body. + * @param body + * @param a + * @return + */ + static public function getShapeFromArbiter(body:Body, a:Arbiter):Shape + { + if (a.body1 == body || a.body2 == body) + if (a.shape1 && a.shape1.body == body) + return a.shape1; + else if (a.shape2 && a.shape2.body == body) + return a.shape2; + + return null; + } + + /** + * In Nape we are blind concerning the collision, we are never sure which body is the collider. This function should help. + * Call this function to obtain the object of the type wanted. + * @param objectClass the class whose you want to pick up the object. + * @param callback the InteractionCallback. + * @return the object of the class desired. + */ + static public function CollisionGetObjectByType(objectClass:Class, callback:InteractionCallback):NapePhysicsObject { + return callback.int1.userData.myData is objectClass ? callback.int1.userData.myData : callback.int2.userData.myData + } + } +} diff --git a/src/citrus/physics/simple/SimpleCitrusSolver.as b/src/citrus/physics/simple/SimpleCitrusSolver.as new file mode 100644 index 00000000..6cceade0 --- /dev/null +++ b/src/citrus/physics/simple/SimpleCitrusSolver.as @@ -0,0 +1,249 @@ +package citrus.physics.simple { + + import citrus.core.CitrusEngine; + import citrus.core.CitrusObject; + import citrus.math.MathVector; + import citrus.objects.CitrusSprite; + + /** + * The CitrusSolver is a simple math-based collision-detection system built for doing simple collision detection in games where physics needs are light + * and physics engine are overkill (also useful for mobile). The Citrus Solver works with the CitrusSprite objects, and uses their x, y, width, and height properties to + * report and adjust for collisions. + * + *

    The CitrusSolver is not useful for the following cases: 1) Rotated (non-axis-aligned) objects, angular velocity, mass-based collision reactions, and dynamic-to-dynamic object + * collisions (only static-to-dynamic works). If you need any of those physics features, you should use Box2D instead. + * If you only need to know if an overlap occurred and you don't need to solve the collision, then you may test collisions between two dynamic + * (moving) objects.

    + * + *

    After you create your CitrusSolver instance, you will want to call the collide and/or overlap methods to tell the solver which object types to test for collisions/overlaps + * against. See the documentation for those two classes for more info.

    + */ + public class SimpleCitrusSolver extends CitrusObject { + + private var _collideChecks:Array = []; + private var _overlapChecks:Array = []; + + public function SimpleCitrusSolver(name:String, params:Object = null) { + + updateCallEnabled = true; + + super(name, params); + + _ce = CitrusEngine.getInstance(); + } + + /** + * Call this method once after the CitrusSolver constructor to tell the solver to report (and solve) collisions between the two specified objects. + * The CitrusSolver will then automatically test collisions between any game object of the specified type once per frame. + * You can only test collisions between a dynamic (movable) object and a static (non-movable) object. + * @param dynamicObjectType The object that will be moved away from overlapping during a collision (probably your hero or something else that moves). + * @param staticObjectType The object that does not move (probably your platform or wall, etc). + */ + public function collide(dynamicObjectType:Class, staticObjectType:Class):void { + + _collideChecks.push({a:dynamicObjectType, b:staticObjectType}); + } + + /** + * Call this method once after the CitrusSolver constructor to tell the solver to report overlaps between the two specified objects. + * The CitrusSolver will then automatically test overlaps between any game object of the specified type once per frame. + * With overlaps, you ARE allowed to test between two dynamic (moving) objects. + * @param typeA The first type of object you want to test for collisions against the second object type. + * @param typeB The second type of object you want to test for collisions against the first object type. + */ + public function overlap(typeA:Class, typeB:Class):void { + + _overlapChecks.push({a:typeA, b:typeB}); + } + + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + + for each (var pair:Object in _collideChecks) { + + if (pair.a == pair.b) { + throw new Error("CitrusSolver does not test collisions against objects of the same type."); + } else { + // compare A's to B's + var groupA:Vector. = _ce.state.getObjectsByType(pair.a); + + for (var i:uint = 0; i < groupA.length; ++i) { + + var itemA:CitrusSprite = groupA[i] as CitrusSprite; + var groupB:Vector. = _ce.state.getObjectsByType(pair.b); + + for (var j:uint = 0; j < groupB.length; ++j) { + + var itemB:CitrusSprite = groupB[j] as CitrusSprite; + collideOnce(itemA, itemB); + } + } + } + } + + for each (pair in _overlapChecks) { + + if (pair.a == pair.b) { + // compare A's to each other + var group:Vector. = _ce.state.getObjectsByType(pair.a); + + for (i = 0; i < groupA.length; ++i) { + + itemA = group[i] as CitrusSprite; + + for (j = i + 1; j < group.length; ++j) { + + itemB = group[j] as CitrusSprite; + overlapOnce(itemA, itemB); + } + } + + } else { + // compare A's to B's + groupA = _ce.state.getObjectsByType(pair.a); + + for (i = 0; i < groupA.length; ++i) { + + itemA = groupA[i] as CitrusSprite; + groupB = _ce.state.getObjectsByType(pair.b); + + for (j = 0; j < groupB.length; ++j) { + + itemB = groupB[j] as CitrusSprite; + overlapOnce(itemA, itemB); + } + } + } + } + } + + public function collideOnce(a:CitrusSprite, b:CitrusSprite):Boolean { + + var diffX:Number = (a.width / 2 + b.width / 2) - Math.abs(a.x - b.x); + if (diffX >= 0) { + + var diffY:Number = (a.height / 2 + b.height / 2) - Math.abs(a.y - b.y); + if (diffY >= 0) { + + var collisionType:uint; + var impact:Number; + var normal:Number; + + if (diffX < diffY) { + // horizontal collision + + if (a.x < b.x) { + a.x -= diffX; + normal = 1; + + if (a.velocity.x > 0) + a.velocity.x = 0; + + } else { + a.x += diffX; + normal = -1; + + if (a.velocity.x < 0) + a.velocity.x = 0; + } + + impact = Math.abs(b.velocity.x - a.velocity.x); + + if (!a.collisions[b]) { + + a.collisions[b] = new SimpleCollision(a, b, new MathVector(normal, 0), -impact, SimpleCollision.BEGIN); + a.onCollide.dispatch(a, b, new MathVector(0, normal), -impact); + + b.collisions[a] = new SimpleCollision(b, a, new MathVector(-normal, 0), impact, SimpleCollision.BEGIN); + b.onCollide.dispatch(b, a, new MathVector(0, -normal), impact); + + } else { + + a.collisions[b].type = SimpleCollision.PERSIST; + a.collisions[b].impact = impact; + a.collisions[b].normal.x = normal; + a.collisions[b].normal.y = 0; + a.onPersist.dispatch(a, b, a.collisions[b].normal); + + b.collisions[a].type = SimpleCollision.PERSIST; + b.collisions[a].impact = -impact; + b.collisions[a].normal.x = -normal; + b.collisions[a].normal.y = 0; + b.onPersist.dispatch(b, a, b.collisions[a].normal); + } + + } else { + // vertical collision + + if (a.y < b.y) { + a.y -= diffY; + normal = 1; + + if (a.velocity.y > 0) + a.velocity.y = 0; + + } else { + a.y += diffY; + normal = -1; + + if (a.velocity.y < 0) + a.velocity.y = 0; + } + + impact = Math.abs(b.velocity.y - a.velocity.y); + + if (!a.collisions[b]) { + + a.collisions[b] = new SimpleCollision(a, b, new MathVector(0, normal), -impact, SimpleCollision.BEGIN); + a.onCollide.dispatch(a, b, new MathVector(0, normal), -impact); + + b.collisions[a] = new SimpleCollision(b, a, new MathVector(0, -normal), impact, SimpleCollision.BEGIN); + b.onCollide.dispatch(b, a, new MathVector(0, -normal), impact); + + } else { + + a.collisions[b].type = SimpleCollision.PERSIST; + a.collisions[b].impact = impact; + a.collisions[b].normal.x = 0; + a.collisions[b].normal.y = normal; + a.onPersist.dispatch(a, b, a.collisions[b].normal); + + b.collisions[a].type = SimpleCollision.PERSIST; + b.collisions[a].impact = -impact; + b.collisions[a].normal.x = 0; + b.collisions[a].normal.y = -normal; + b.onPersist.dispatch(b, a, b.collisions[a].normal); + } + } + + return true; + } + } + + if (a.collisions[b]) { + + a.onSeparate.dispatch(a, b); + delete a.collisions[b]; + + b.onSeparate.dispatch(b, a); + delete b.collisions[a]; + } + + return false; + } + + public function overlapOnce(a:CitrusSprite, b:CitrusSprite):Boolean { + + var overlap:Boolean = (a.x + a.width / 2 >= b.x - b.width / 2 && a.x - a.width / 2 <= b.x + b.width / 2 && // x axis overlaps + a.y + a.height / 2 >= b.y - b.height / 2 && a.y - a.height / 2 <= b.y + b.height / 2); // y axis overlaps + + if (overlap) { + a.onCollide.dispatch(a, b, null, 0); + b.onCollide.dispatch(b, a, null, 0); + } + + return overlap; + } + } +} \ No newline at end of file diff --git a/src/citrus/physics/simple/SimpleCollision.as b/src/citrus/physics/simple/SimpleCollision.as new file mode 100644 index 00000000..158353ea --- /dev/null +++ b/src/citrus/physics/simple/SimpleCollision.as @@ -0,0 +1,28 @@ +package citrus.physics.simple +{ + + import citrus.math.MathVector; + import citrus.objects.CitrusSprite; + + public class SimpleCollision + { + public static const BEGIN:uint = 0; + public static const PERSIST:uint = 1; + + public var self:CitrusSprite; + public var other:CitrusSprite; + public var normal:MathVector; + public var impact:Number; + public var type:uint; + + public function SimpleCollision(self:CitrusSprite, other:CitrusSprite, normal:MathVector, impact:Number, type:uint) + { + this.self = self; + this.other = other; + this.normal = normal; + this.impact = impact; + this.type = type; + } + } + +} \ No newline at end of file diff --git a/src/citrus/sounds/CitrusSound.as b/src/citrus/sounds/CitrusSound.as new file mode 100644 index 00000000..65c47a39 --- /dev/null +++ b/src/citrus/sounds/CitrusSound.as @@ -0,0 +1,398 @@ +package citrus.sounds +{ + + import citrus.core.CitrusEngine; + import citrus.events.CitrusEvent; + import citrus.events.CitrusEventDispatcher; + import citrus.events.CitrusSoundEvent; + import flash.events.ErrorEvent; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.media.Sound; + import flash.media.SoundTransform; + import flash.net.URLRequest; + + import citrus.core.citrus_internal; + + public class CitrusSound extends CitrusEventDispatcher + { + use namespace citrus_internal; + + public var hideParamWarnings:Boolean = false; + + protected var _name:String; + protected var _soundTransform:SoundTransform; + protected var _sound:Sound; + protected var _ioerror:Boolean = false; + protected var _loadedRatio:Number = 0; + protected var _loaded:Boolean = false; + protected var _group:CitrusSoundGroup; + protected var _isPlaying:Boolean = false; + protected var _urlReq:URLRequest; + protected var _volume:Number = 1; + protected var _panning:Number = 0; + protected var _mute:Boolean = false; + protected var _paused:Boolean = false; + + protected var _ce:CitrusEngine; + + /** + * times to loop : + * if negative, infinite looping will be done and loops won't be tracked in CitrusSoundInstances. + * if you want to loop infinitely and still keep track of loops, set loops to int.MAX_VALUE instead, each time a loop completes + * the SOUND_LOOP event would be fired and loops will be counted. + */ + public var loops:int = 0; + + /** + * a list of all CitrusSoundInstances that are active (playing or paused) + */ + internal var soundInstances:Vector.; + + /** + * if permanent is set to true, no new CitrusSoundInstance + * will stop a sound instance from this CitrusSound to free up a channel. + * it is a good idea to set background music as 'permanent' + */ + public var permanent:Boolean = false; + + /** + * When the CitrusSound is constructed, it will load itself. + */ + public var autoload:Boolean = false; + + public function CitrusSound(name:String,params:Object = null) + { + _ce = CitrusEngine.getInstance(); + _ce.sound.addDispatchChild(this); + + _name = name; + if (!("sound" in params) || params["sound"] == null) + throw new Error(String(String(this) + " sound "+ name+ " has no sound param defined.")); + + soundInstances = new Vector.(); + + setParams(params); + + if (autoload) + load(); + } + + public function load():void + { + unload(); + if (_urlReq && _loadedRatio == 0 && !_sound.isBuffering) + { + _ioerror = false; + _loaded = false; + _sound.addEventListener(IOErrorEvent.IO_ERROR, onIOError); + _sound.addEventListener(ProgressEvent.PROGRESS, onProgress); + _sound.load(_urlReq); + } + } + + public function unload():void + { + if(_sound.isBuffering) + _sound.close(); + _sound.removeEventListener(IOErrorEvent.IO_ERROR, onIOError); + _sound.removeEventListener(ProgressEvent.PROGRESS, onProgress); + sound = _urlReq; + } + + public function play():CitrusSoundInstance + { + return new CitrusSoundInstance(this, true, true); + } + + /** + * creates a sound instance from this CitrusSound. + * you can use this CitrusSoundInstance to play at a specific position and control its volume/panning. + * @param autoplay + * @param autodestroy + * @return CitrusSoundInstance + */ + public function createInstance(autoplay:Boolean = false,autodestroy:Boolean = true):CitrusSoundInstance + { + return new CitrusSoundInstance(this, autoplay, autodestroy); + } + + public function resume():void + { + var soundInstance:CitrusSoundInstance; + for each (soundInstance in soundInstances) + if(soundInstance.isPaused) + soundInstance.resume(); + } + + public function pause():void + { + var soundInstance:CitrusSoundInstance; + for each (soundInstance in soundInstances) + if(soundInstance.isPlaying) + soundInstance.pause(); + } + + public function stop():void + { + var soundInstance:CitrusSoundInstance; + for each (soundInstance in soundInstances) + if(soundInstance.isPlaying || soundInstance.isPaused) + soundInstance.stop(); + } + + protected function onIOError(event:ErrorEvent):void + { + unload(); + trace("CitrusSound Error Loading: ", this.name); + _ioerror = true; + dispatchEvent(new CitrusSoundEvent(CitrusSoundEvent.SOUND_ERROR, this, null) as CitrusEvent); + } + + protected function onProgress(event:ProgressEvent):void + { + _loadedRatio = _sound.bytesLoaded / _sound.bytesTotal; + if (_loadedRatio == 1) + { + _loaded = true; + dispatchEvent(new CitrusSoundEvent(CitrusSoundEvent.SOUND_LOADED,this,null)); + } + } + + internal function resetSoundTransform(applyToInstances:Boolean = false):SoundTransform + { + if (_soundTransform == null) + _soundTransform = new SoundTransform(); + + if (_group != null) + { + _soundTransform.volume = (_mute || _group._mute || _ce.sound.masterMute) ? 0 : _volume * _group._volume * _ce.sound.masterVolume; + _soundTransform.pan = _panning; + + }else + { + _soundTransform.volume = (_mute || _ce.sound.masterMute) ? 0 : _volume * _ce.sound.masterVolume; + _soundTransform.pan = _panning; + } + + if (applyToInstances) + { + var soundInstance:CitrusSoundInstance; + for each (soundInstance in soundInstances) + soundInstance.resetSoundTransform(false); + } + + return _soundTransform; + } + + public function set sound(val:Object):void + { + if (_sound) + { + _sound.removeEventListener(IOErrorEvent.IO_ERROR, onIOError); + _sound.removeEventListener(ProgressEvent.PROGRESS, onProgress); + } + + if (val is String) + { + _urlReq = new URLRequest(val as String); + _sound = new Sound(); + } + else if (val is Class) + { + _sound = new (val as Class)(); + _ioerror = false; + _loadedRatio = 1; + _loaded = true; + } + else if (val is Sound) + { + _sound = val as Sound; + _loadedRatio = 1; + _loaded = true; + } + else if (val is URLRequest) + { + _urlReq = val as URLRequest; + _sound = new Sound(); + } + else + throw new Error("CitrusSound, " + val + "is not a valid sound paramater"); + } + + public function get sound():Object + { + return _sound; + } + + public function get isPlaying():Boolean + { + var soundInstance:CitrusSoundInstance; + for each (soundInstance in soundInstances) + if (soundInstance.isPlaying) + return true; + return false; + } + + public function get isPaused():Boolean + { + var soundInstance:CitrusSoundInstance; + for each (soundInstance in soundInstances) + if (soundInstance.isPaused) + return true; + return false; + } + + public function get group():* + { + return _group; + } + + public function set volume(val:Number):void + { + if (_volume != val) + { + _volume = val; + resetSoundTransform(true); + } + } + + public function set panning(val:Number):void + { + if (_panning != val) + { + _panning = val; + resetSoundTransform(true); + } + } + + public function set mute(val:Boolean):void + { + if (_mute != val) + { + _mute = val; + resetSoundTransform(true); + } + } + + public function set group(val:*):void + { + _group = CitrusEngine.getInstance().sound.getGroup(val); + if (_group) + _group.addSound(this); + } + + public function setGroup(val:CitrusSoundGroup):void + { + _group = val; + } + + internal function destroy():void + { + if (_sound) + { + _sound.removeEventListener(IOErrorEvent.IO_ERROR, onIOError); + _sound.removeEventListener(ProgressEvent.PROGRESS, onProgress); + } + if (_group) + _group.removeSound(this); + _soundTransform = null; + _sound = null; + + var soundInstance:CitrusSoundInstance; + for each (soundInstance in soundInstances) + soundInstance.stop(); + + removeEventListeners(); + _ce.sound.removeDispatchChild(this); + } + + public function get loadedRatio():Number + { + return _loadedRatio; + } + + public function get loaded():Boolean + { + return _loaded; + } + + public function get ioerror():Boolean + { + return _ioerror; + } + + public function get volume():Number + { + return _volume; + } + + public function get panning():Number + { + return _panning; + } + + public function get mute():Boolean + { + return _mute; + } + + public function get name():String + { + return _name; + } + + public function get soundTransform():SoundTransform + { + return _soundTransform; + } + + public function get ready():Boolean + { + if (_sound) + { + if (_sound.isURLInaccessible) + return false; + if (_sound.isBuffering || _loadedRatio > 0) + return true; + } + return false; + } + + public function get instances():Vector. + { + return soundInstances.slice(); + } + + public function getInstance(index:int):CitrusSoundInstance + { + if (soundInstances.length > index + 1) + return soundInstances[index]; + return null; + } + + protected function setParams(params:Object):void + { + for (var param:String in params) + { + try + { + if (params[param] == "true") + this[param] = true; + else if (params[param] == "false") + this[param] = false; + else + this[param] = params[param]; + } + catch (e:Error) + { + trace(e.message); + if (!hideParamWarnings) + trace("Warning: The parameter " + param + " does not exist on " + this); + } + } + } + + } + +} \ No newline at end of file diff --git a/src/citrus/sounds/CitrusSoundDebugArt.as b/src/citrus/sounds/CitrusSoundDebugArt.as new file mode 100644 index 00000000..fe161dec --- /dev/null +++ b/src/citrus/sounds/CitrusSoundDebugArt.as @@ -0,0 +1,24 @@ +package citrus.sounds +{ + import flash.display.Sprite; + + /** + * flash.display.Sprite drawn onto by CitrusSoundSpace. + */ + public class CitrusSoundDebugArt extends Sprite + { + + public function CitrusSoundDebugArt() + { + mouseEnabled = false; + mouseChildren = false; + } + + public function destroy():void + { + + } + + } + +} \ No newline at end of file diff --git a/src/citrus/sounds/CitrusSoundGroup.as b/src/citrus/sounds/CitrusSoundGroup.as new file mode 100644 index 00000000..d8ab637f --- /dev/null +++ b/src/citrus/sounds/CitrusSoundGroup.as @@ -0,0 +1,173 @@ +package citrus.sounds +{ + import citrus.events.CitrusEventDispatcher; + import citrus.events.CitrusSoundEvent; + import citrus.math.MathUtils; + + import citrus.core.citrus_internal; + /** + * CitrusSoundGroup represents a volume group with its groupID and has mute control as well. + */ + public class CitrusSoundGroup extends CitrusEventDispatcher + { + + public static const BGM:String = "BGM"; + public static const SFX:String = "SFX"; + public static const UI:String = "UI"; + + protected var _groupID:String; + + internal var _volume:Number = 1; + internal var _mute:Boolean = false; + + protected var _sounds:Vector.; + + public var polyphonic:Boolean = true; + + public function CitrusSoundGroup() + { + _sounds = new Vector.(); + } + + protected function applyChanges():void + { + var s:CitrusSound; + for each(s in _sounds) + s.resetSoundTransform(true); + } + + internal function addSound(s:CitrusSound):void + { + if (s.group && s.group.isadded(s)) + (s.group as CitrusSoundGroup).removeSound(s); + s.setGroup(this); + _sounds.push(s); + s.addEventListener(CitrusSoundEvent.SOUND_LOADED, handleSoundLoaded); + } + + internal function isadded(sound:CitrusSound):Boolean + { + var s:CitrusSound; + for each(s in _sounds) + if (sound == s) + return true; + return false; + } + + public function getAllSounds():Vector. + { + return _sounds.slice(); + } + + public function preloadSounds():void + { + var s:CitrusSound; + for each(s in _sounds) + if(!s.loaded) + s.load(); + } + + public function stopAllSounds():void + { + var s:CitrusSound; + for each(s in _sounds) + s.stop(); + } + + internal function removeSound(s:CitrusSound):void + { + var si:String; + var cs:CitrusSound; + for (si in _sounds) + { + if (_sounds[si] == s) + { + cs = _sounds[si]; + cs.setGroup(null); + cs.resetSoundTransform(true); + cs.removeEventListener(CitrusSoundEvent.SOUND_LOADED, handleSoundLoaded); + _sounds.splice(uint(si), 1); + break; + } + } + } + + public function getSound(name:String):CitrusSound + { + var s:CitrusSound; + for each(s in _sounds) + if (s.name == name) + return s; + return null; + } + + public function getRandomSound():CitrusSound + { + var index:uint = MathUtils.randomInt(0, _sounds.length - 1); + return _sounds[index]; + } + + protected function handleSoundLoaded(e:CitrusSoundEvent):void + { + var cs:CitrusSound; + for each(cs in _sounds) + { + if (!cs.loaded) + return; + } + dispatchEvent(new CitrusSoundEvent(CitrusSoundEvent.ALL_SOUNDS_LOADED, e.sound, null)); + } + + public function set mute(val:Boolean):void + { + _mute = val; + applyChanges(); + } + + public function get mute():Boolean + { + return _mute; + } + + public function set volume(val:Number):void + { + _volume = val; + applyChanges(); + } + + public function get volume():Number + { + return _volume; + } + + public function get isPlaying():Boolean + { + for each(var s:CitrusSound in _sounds) + if(s.isPlaying) + return true; + + return false; + } + + citrus_internal function setGroupID(value:String):void + { + _groupID = value; + } + + public function get groupID():String + { + return _groupID; + } + + internal function destroy():void + { + var s:CitrusSound; + for each(s in _sounds) + removeSound(s); + _sounds.length = 0; + removeEventListeners(); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/sounds/CitrusSoundInstance.as b/src/citrus/sounds/CitrusSoundInstance.as new file mode 100644 index 00000000..1ed70a12 --- /dev/null +++ b/src/citrus/sounds/CitrusSoundInstance.as @@ -0,0 +1,499 @@ +package citrus.sounds +{ + import citrus.core.CitrusEngine; + import citrus.events.CitrusEvent; + import citrus.events.CitrusEventDispatcher; + import citrus.events.CitrusSoundEvent; + import citrus.utils.SoundChannelUtil; + import flash.events.Event; + import flash.media.Sound; + import flash.media.SoundChannel; + import flash.media.SoundTransform; + import citrus.core.citrus_internal; + + /** + * CitrusSoundInstance + * this class represents an existing sound (playing, paused or stopped) + * it holds a reference to the CitrusSound it was created from and + * a sound channel. through a CitrusSoundInstance you can tweak volumes and panning + * individually instead of CitrusSound wide. + * + * a paused sound is still considered active, and keeps a soundChannel alive to be able to resume later. + */ + public class CitrusSoundInstance extends CitrusEventDispatcher + { + use namespace citrus_internal; + + public var data:Object = { }; + + protected var _ID:uint = 0; + protected static var last_id:uint = 0; + + protected var _name:String; + protected var _parentsound:CitrusSound; + protected var _soundTransform:SoundTransform; + + protected var _permanent:Boolean = false; + + protected var _volume:Number = 1; + protected var _panning:Number = 0; + + protected var _soundChannel:SoundChannel; + + protected var _isPlaying:Boolean = false; + protected var _isPaused:Boolean = false; + protected var _isActive:Boolean = false; + protected var _loops:int = 0; + protected var _loopCount:int = 0; + protected var _last_position:Number = 0; + protected var _destroyed:Boolean = false; + + protected var _ce:CitrusEngine; + + /** + * if autodestroy is true, when the sound ends, destroy will be called instead of just stop(). + */ + protected var _autodestroy:Boolean; + + /** + * list of active sound instances + */ + protected static var _list:Vector. = new Vector.(); + + /** + * list of active non permanent sound instances + */ + protected static var _nonPermanent:Vector. = new Vector.(); + + /** + * What to do when no new sound channel is available? + * remove the first played instance, the last, or simply don't play the sound. + * @see REMOVE_FIRST_PLAYED + * @see REMOVE_LAST_PLAYED + * @see DONT_PLAY + */ + public static var onNewChannelsUnavailable:String = REMOVE_FIRST_PLAYED; + + /** + * offset to use on all sounds when playing them via Sound.play(startPosition...). + * + * If all of your sounds are encoded using the same encoder (that's important otherwise the silence could be variable), + * and you are able to identify the amount of silence in ms that there is at the beginning of them, + * set startPositionOffset to that value you found. + * + * This will get rid of most if not all the gaps in looping and non looping sounds. + * + * Warning: it won't get rid of the gaps caused by loading/streaming or event processing. + */ + public static var startPositionOffset:Number = 0; + + /** + * trace all events dispatched from CitrusSoundInstances + */ + public static var eventVerbose:Boolean = false; + + protected static var _maxChannels:uint = SoundChannelUtil.maxAvailableChannels(); + public static function get maxChannels():uint { return _maxChannels; }; + + { + if(maxChannels < 32) + trace("[CitrusSoundInstance] maximum number of concurrent SoundChannels for this instance of CitrusEngine :", maxChannels); + } + + public function CitrusSoundInstance(parentsound:CitrusSound, autoplay:Boolean = true, autodestroy:Boolean = true) + { + _parentsound = parentsound; + _permanent = _parentsound.permanent; + _soundTransform = _parentsound.resetSoundTransform(); + + _ID = last_id++; + + _parentsound.addDispatchChild(this); + + _ce = CitrusEngine.getInstance(); + + _name = _parentsound.name; + _loops = _parentsound.loops; + _autodestroy = autodestroy; + + if (autoplay) + play(); + } + + public function play():void + { + if (_destroyed) + return; + + if (!_isPaused || !_isPlaying) + playAt(startPositionOffset); + } + + public function playAt(position:Number):void + { + if (_destroyed) + return; + + var soundInstance:CitrusSoundInstance; + + if (_parentsound.group && !_parentsound.group.polyphonic) + CitrusSoundGroup(_parentsound.group).stopAllSounds(); + + //check if the same CitrusSound is already playing and is permanent (if so, no need to play a second one) + if (_permanent) + for each(soundInstance in _list) + if (soundInstance._name == this._name) + { + dispatcher(CitrusSoundEvent.NO_CHANNEL_AVAILABLE); + stop(true); + return; + } + + //check if channels are available, if not, free some up (as long as instances are not permanent) + if (_list.length >= maxChannels) + { + var len:int; + var i:int; + switch (onNewChannelsUnavailable) + { + case REMOVE_FIRST_PLAYED: + for (i = 0; i < _nonPermanent.length - 1; i++) + { + soundInstance = _nonPermanent[i]; + if (soundInstance && !soundInstance.isPaused) + soundInstance.stop(true); + if (_list.length + 1 > _maxChannels) + i = 0; + else + break; + } + break; + case REMOVE_LAST_PLAYED: + for (i = _nonPermanent.length-1; i > -1; i--) + { + soundInstance = _nonPermanent[i]; + if (soundInstance && !soundInstance.isPaused) + soundInstance.stop(true); + if (_list.length + 1 > _maxChannels) + i = _nonPermanent.length-1; + else + break; + } + break; + case DONT_PLAY: + dispatcher(CitrusSoundEvent.NO_CHANNEL_AVAILABLE); + stop(true); + return; + } + } + + if (!_parentsound.ready) + { + dispatcher(CitrusSoundEvent.SOUND_NOT_READY); + _parentsound.load(); + } + + if (_list.length >= _maxChannels) + { + dispatcher(CitrusSoundEvent.NO_CHANNEL_AVAILABLE); + stop(true); + return; + } + + _isActive = true; + + soundChannel = (_parentsound.sound as Sound).play(position, (_loops < 0) ? int.MAX_VALUE : 0); + + _isPlaying = true; + _isPaused = false; + + resetSoundTransform(); + + _list.unshift(this); + + if (!_permanent) + _nonPermanent.unshift(this); + + _parentsound.soundInstances.unshift(this); + + if ((position == 0 || position == startPositionOffset) && _loopCount == 0) + dispatcher(CitrusSoundEvent.SOUND_START); + } + + public function pause():void + { + if (!_isActive) + return; + + if (_soundChannel) + { + _last_position = _soundChannel.position; + _soundChannel.stop(); + } + + soundChannel = _parentsound.sound.play(0, int.MAX_VALUE); + + _isPlaying = false; + _isPaused = true; + + resetSoundTransform(); + + dispatcher(CitrusSoundEvent.SOUND_PAUSE); + } + + public function resume():void + { + if (!_isActive) + return; + + _soundChannel.stop(); + soundChannel = _parentsound.sound.play(_last_position, 0); + + _isPlaying = true; + _isPaused = false; + + resetSoundTransform() + + dispatcher(CitrusSoundEvent.SOUND_RESUME); + } + + public function stop(forced:Boolean = false):void + { + if (_destroyed) + return; + + if(_soundChannel) + _soundChannel.stop(); + soundChannel = null; + + _isPlaying = false; + _isPaused = false; + _isActive = false; + + _loopCount = 0; + + removeSelfFromVector(_list); + removeSelfFromVector(_nonPermanent); + removeSelfFromVector(_parentsound.soundInstances); + + if (forced) + dispatcher(CitrusSoundEvent.FORCE_STOP); + + dispatcher(CitrusSoundEvent.SOUND_END); + + if (_autodestroy) + destroy(); + } + + public function destroy(forced:Boolean = false):void + { + _parentsound.removeDispatchChild(this); + + _parentsound = null; + _soundTransform = null; + data = null; + soundChannel = null; + + removeEventListeners(); + + _destroyed = true; + } + + + protected function onComplete(e:Event):void + { + + if (_isPaused) + { + soundChannel = _parentsound.sound.play(0, int.MAX_VALUE); + return; + } + + _loopCount++; + + if (_loops < 0) + { + _soundChannel.stop(); + soundChannel = (_parentsound.sound as Sound).play(startPositionOffset, int.MAX_VALUE); + resetSoundTransform() + } + else if (_loopCount > _loops) + stop(); + else + { + _soundChannel.stop(); + soundChannel = (_parentsound.sound as Sound).play(startPositionOffset, 0); + resetSoundTransform(); + dispatcher(CitrusSoundEvent.SOUND_LOOP); + } + } + + public function set volume(value:Number):void + { + _volume = value; + resetSoundTransform(); + } + + public function get volume():Number + { + return _volume; + } + + public function set panning(value:Number):void + { + _panning = value; + resetSoundTransform(); + } + + public function get panning():Number + { + return _panning; + } + + public function setVolumePanning(volume:Number = 1, panning:Number = 0):CitrusSoundInstance + { + _volume = volume; + _panning = panning; + resetSoundTransform(); + return this; + } + + /** + * removes self from given vector. + * @param list Vector.<CitrusSoundInstance> + */ + public function removeSelfFromVector(list:Vector.):void + { + var i:String; + for (i in list) + if (list[i] == this) + { + list[i] = null; + list.splice(int(i), 1); + return; + } + } + + /** + * a vector of all currently playing CitrusSoundIntance objects + */ + public static function get activeSoundInstances():Vector. + { + return _list.slice(); + } + + /** + * use this setter when creating a new soundChannel + * it will automaticaly add/remove event listeners from the protected _soundChannel + */ + internal function set soundChannel(channel:SoundChannel):void + { + if (_soundChannel) + _soundChannel.removeEventListener(Event.SOUND_COMPLETE, onComplete,true); + if (channel) + channel.addEventListener(Event.SOUND_COMPLETE, onComplete); + + _soundChannel = channel; + } + + public function getSoundChannel():SoundChannel + { + return _soundChannel; + } + + internal function get soundChannel():SoundChannel + { + return _soundChannel; + } + + public function get leftPeak():Number + { + if (_soundChannel) + return _soundChannel.leftPeak; + return 0; + } + + public function get rightPeak():Number + { + if (_soundChannel) + return _soundChannel.rightPeak; + return 0; + } + + public function get parentsound():CitrusSound + { + return _parentsound; + } + + public function get ID():uint + { + return _ID; + } + + public function get isPlaying():Boolean + { + return _isPlaying; + } + + public function get isPaused():Boolean + { + return _isPaused; + } + + internal function get isActive():Boolean + { + return _isActive; + } + + public function get loopCount():uint + { + return _loopCount; + } + + public function get loops():int + { + return _loops; + } + + /** + * dispatches CitrusSoundInstance + */ + internal function dispatcher(type:String):void + { + var event:CitrusEvent = new CitrusSoundEvent(type, _parentsound, this, ID) as CitrusEvent; + dispatchEvent(event); + if (eventVerbose) + trace(event); + } + + internal function get destroyed():Boolean + { + return _destroyed; + } + + internal function resetSoundTransform(parentSoundTransformReset:Boolean = true):SoundTransform + { + _soundTransform = parentSoundTransformReset ? _parentsound.resetSoundTransform() : _parentsound.soundTransform; + _soundTransform.volume *= _volume; + _soundTransform.pan = _panning; + + if (_soundChannel) + if (_isPaused) + return _soundChannel.soundTransform = SoundChannelUtil.silentST; + else + return _soundChannel.soundTransform = _soundTransform; + else + return _soundTransform; + } + + public function toString():String + { + return "CitrusSoundInstance name:" + _name + " id:" + _ID + " playing:" + _isPlaying + " paused:" + _isPaused + "\n"; + } + + public static const REMOVE_LAST_PLAYED:String = "REMOVE_LAST_PLAYED"; + public static const REMOVE_FIRST_PLAYED:String = "REMOVE_FIRST_PLAYED"; + public static const DONT_PLAY:String = "DONT_PLAY"; + } + +} \ No newline at end of file diff --git a/src/citrus/sounds/CitrusSoundObject.as b/src/citrus/sounds/CitrusSoundObject.as new file mode 100644 index 00000000..b784f7fb --- /dev/null +++ b/src/citrus/sounds/CitrusSoundObject.as @@ -0,0 +1,288 @@ +package citrus.sounds +{ + import citrus.core.CitrusEngine; + import citrus.events.CitrusSoundEvent; + import citrus.math.MathUtils; + import citrus.math.MathVector; + import citrus.view.ISpriteView; + import flash.geom.Rectangle; + + /** + * sound object in a CitrusSoundSpace + */ + public class CitrusSoundObject + { + protected var _ce:CitrusEngine; + protected var _space:CitrusSoundSpace; + protected var _citrusObject:ISpriteView; + protected var _sounds:Vector. = new Vector.(); + protected var _enabled:Boolean = true; + + public static var panAdjust:Function = MathUtils.easeInCubic; + public static var volAdjust:Function = MathUtils.easeOutQuad; + + protected var _camVec:MathVector = new MathVector(); + protected var _rect:Rectangle = new Rectangle(); + + protected var _volume:Number = 1; + + /** + * radius or this sound object. this determines at what distance will the sound start to get heard. + */ + public var radius:Number = 600; + + public function CitrusSoundObject(citrusObject:ISpriteView) + { + _ce = CitrusEngine.getInstance(); + _space = _ce.state.getFirstObjectByType(CitrusSoundSpace) as CitrusSoundSpace; + if (!_space) + throw new Error("[CitrusSoundObject] for " + citrusObject["name"] + " couldn't find a CitrusSoundSpace."); + + _citrusObject = citrusObject; + _space.add(this); + } + + public function initialize():void + { + + } + + /** + * play a sound through this sound object + * @param sound sound id (String) or CitrusSound + * @return + */ + public function play(sound:*):CitrusSoundInstance + { + var citrusSound:CitrusSound; + var soundInstance:CitrusSoundInstance; + + if (sound is String) + citrusSound = _space.soundManager.getSound(sound); + else if (sound is CitrusSound) + citrusSound = sound; + + if (citrusSound != null) + { + soundInstance = citrusSound.createInstance(false, true); + if (soundInstance) + { + soundInstance.addEventListener(CitrusSoundEvent.SOUND_START, onSoundStart); + soundInstance.addEventListener(CitrusSoundEvent.SOUND_END, onSoundEnd); + soundInstance.play(); + updateSoundInstance(soundInstance, _camVec.length); + } + } + + return soundInstance; + } + + /** + * pause a sound through this sound object + * @param sound sound id (String) or CitrusSound + * @return + */ + public function pause(sound:*):void + { + var citrusSound:CitrusSound; + var soundInstance:CitrusSoundInstance; + + if (sound is String) + citrusSound = _space.soundManager.getSound(sound); + else if (sound is CitrusSound) + citrusSound = sound; + + if(citrusSound) + citrusSound.pause(); + } + + /** + * resume a sound through this sound object + * @param sound sound id (String) or CitrusSound + * @return + */ + public function resume(sound:*):void + { + var citrusSound:CitrusSound; + var soundInstance:CitrusSoundInstance; + + if (sound is String) + citrusSound = _space.soundManager.getSound(sound); + else if (sound is CitrusSound) + citrusSound = sound; + + if (citrusSound) + { + citrusSound.resume(); + updateSoundInstance(soundInstance, _camVec.length); + } + } + + + /** + * stop a sound through this sound object + * @param sound sound id (String) or CitrusSound + * @return + */ + public function stop(sound:*):void + { + var citrusSound:CitrusSound; + var soundInstance:CitrusSoundInstance; + + if (sound is String) + citrusSound = _space.soundManager.getSound(sound); + else if (sound is CitrusSound) + citrusSound = sound; + + if(citrusSound) + citrusSound.stop(); + } + + public function pauseAll():void + { + var soundInstance:CitrusSoundInstance; + for each(soundInstance in _sounds) + soundInstance.pause(); + } + + public function resumeAll():void + { + var soundInstance:CitrusSoundInstance; + for each(soundInstance in _sounds) + soundInstance.resume(); + } + + public function stopAll():void + { + var s:CitrusSoundInstance; + for each (s in _sounds) + s.stop(); + } + + protected function onSoundStart(e:CitrusSoundEvent):void + { + _sounds.push(e.soundInstance); + } + + protected function onSoundEnd(e:CitrusSoundEvent):void + { + e.soundInstance.removeEventListener(CitrusSoundEvent.SOUND_START, onSoundStart); + e.soundInstance.removeEventListener(CitrusSoundEvent.SOUND_END, onSoundEnd); + e.soundInstance.removeSelfFromVector(_sounds); + } + + public function update():void + { + if (_enabled) + updateSounds(); + } + + protected function updateSounds():void + { + var distance:Number = _camVec.length; + var soundInstance:CitrusSoundInstance; + + for each (soundInstance in _sounds) + { + if (!soundInstance.isPlaying) + return; + updateSoundInstance(soundInstance, distance); + } + } + + protected function updateSoundInstance(soundInstance:CitrusSoundInstance,distance:Number = 0):void + { + var volume:Number = distance > radius ? 0 : 1 - distance / radius; + soundInstance.volume = adjustVolume(volume) * _volume; + + var panning:Number = (Math.cos(_camVec.angle) * distance) / + ( (_rect.width /_rect.height) * 0.5 ); + soundInstance.panning = adjustPanning(panning); + } + + public function adjustPanning(value:Number):Number + { + if (value <= -1) + return -1; + else if (value >= 1) + return 1; + + if (value < 0) + return -panAdjust(-value, 0, 1, 1); + else if (value > 0) + return panAdjust(value, 0, 1, 1); + return value; + } + + public function adjustVolume(value:Number):Number + { + if (value <= 0) + return 0; + else if (value >= 1) + return 1; + + return volAdjust(value, 0, 1, 1); + } + + public function destroy():void + { + _space.remove(this); + + var soundInstance:CitrusSoundInstance; + for each(soundInstance in _sounds) + soundInstance.stop(true); + + _sounds.length = 0; + _ce = null; + _camVec = null; + _citrusObject = null; + _space = null; + } + + public function get citrusObject():ISpriteView + { + return _citrusObject; + } + + public function get totalVolume():Number + { + var soundInstance:CitrusSoundInstance; + var total:Number = 0; + for each(soundInstance in _sounds) + total += soundInstance.leftPeak + soundInstance.rightPeak; + if(_sounds.length>0) + total /= _sounds.length * 2; + return total; + } + + public function get rect():Rectangle + { + return _rect; + } + + public function get camVec():MathVector + { + return _camVec; + } + + /** + * volume multiplier for this CitrusSoundObject + */ + public function get volume():Number + { + return _volume; + } + + public function set volume(value:Number):void + { + _volume = value; + } + + public function get activeSoundInstances():Vector. + { + return _sounds.slice(); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/sounds/CitrusSoundSpace.as b/src/citrus/sounds/CitrusSoundSpace.as new file mode 100644 index 00000000..f9d4a1cd --- /dev/null +++ b/src/citrus/sounds/CitrusSoundSpace.as @@ -0,0 +1,254 @@ +package citrus.sounds +{ + import citrus.core.CitrusObject; + import citrus.view.ACitrusCamera; + import citrus.view.ICitrusArt; + import citrus.view.ISpriteView; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + /** + * Experimental spatial sound system + */ + public class CitrusSoundSpace extends CitrusObject implements ISpriteView + { + protected var _visible:Boolean = false; + protected var _touchable:Boolean = false; + protected var _group:uint = 1; + protected var _view:*; + protected var _realDebugView:*; + + protected var _debugArt:CitrusSoundDebugArt; + protected var _objects:Vector.; + protected var _soundManager:SoundManager; + protected var _camera:ACitrusCamera; + + public var drawRadius:Boolean = false; + public var drawObject:Boolean = true; + + public function CitrusSoundSpace(name:String, params:Object = null) { + + super(name, params); + updateCallEnabled = true; + touchable = false; + _soundManager = _ce.sound; + _objects = new Vector.(); + + updateCameraProperties(); + } + + public function add(citrusSoundObject:CitrusSoundObject):void + { + _objects.push(citrusSoundObject); + updateObject(citrusSoundObject); + citrusSoundObject.initialize(); + } + + public function remove(citrusSoundObject:CitrusSoundObject):void + { + var i:int = _objects.indexOf(citrusSoundObject); + if (i > -1) + _objects.splice(i, 1); + } + + protected var camCenter:Point = new Point(); + protected var camRect:Rectangle = new Rectangle(); + protected var camRotation:Number = 0; + + protected function updateCameraProperties():void + { + _camera = _ce.state.view.camera; + camRect.copyFrom(_camera.getRect()); + camCenter.setTo(camRect.x + camRect.width * 0.5, camRect.y + camRect.height * 0.5); + camRotation = _camera.getRotation(); + } + + override public function update(timeDelta:Number):void + { + super.update(timeDelta); + + updateCameraProperties(); + + if (_visible) + _debugArt.graphics.clear(); + + var object:CitrusSoundObject; + for each(object in _objects) + { + updateObject(object); + + if (_visible) + { + if (drawObject) + { + _debugArt.graphics.lineStyle(0.1, 0xFF0000, 0.8); + _debugArt.graphics.drawCircle(object.citrusObject.x, object.citrusObject.y, 1 + 120 * object.totalVolume); + } + if (drawRadius) + { + _debugArt.graphics.lineStyle(0.5, 0x00FF00, 0.8); + _debugArt.graphics.drawCircle(object.citrusObject.x, object.citrusObject.y, object.radius); + } + } + } + + if (_visible) + { + var m:Matrix = _debugArt.transform.matrix; + m.copyFrom(_camera.transformMatrix); + m.concat(_ce.transformMatrix); + _debugArt.transform.matrix = m; + } + } + + protected function updateObject(object:CitrusSoundObject):void + { + if (_camera) + { + object.camVec.setTo(object.citrusObject.x - camCenter.x, object.citrusObject.y - camCenter.y); + object.camVec.angle += camRotation; + object.rect.width = _camera.cameraLensWidth; + object.rect.height = _camera.camProxy.scale; + } + object.update(); + } + + override public function destroy():void + { + visible = false; + _camera = null; + _soundManager = null; + _objects.length = 0; + super.destroy(); + } + + public function get soundManager():SoundManager + { + return _soundManager; + } + + public function getBody():* { + return null; + } + + public function get view():* { + return _view; + } + + public function set view(value:*):void { + _view = value; + } + + public function get x():Number { + return 0; + } + + public function get y():Number { + return 0; + } + + public function get z():Number { + return 0; + } + + public function get width():Number { + return 0; + } + + public function get height():Number { + return 0; + } + + public function get depth():Number { + return 0; + } + + public function get velocity():Array { + return null; + } + + public function get parallaxX():Number { + return 1; + } + + public function get parallaxY():Number { + return 1; + } + + public function get rotation():Number { + return 0; + } + + public function get group():uint { + return _group; + } + + public function set group(value:uint):void { + _group = value; + } + + public function get visible():Boolean { + return _visible; + } + + public function set visible(value:Boolean):void { + if (value == _visible) + return; + + if (value) + { + _debugArt = new CitrusSoundDebugArt(); + _ce.stage.addChild(_debugArt); + } + else if (_debugArt) + { + _debugArt.destroy(); + _ce.stage.removeChild(_debugArt); + } + + _visible = value; + } + + public function get touchable():Boolean { + return _touchable; + } + + public function set touchable(value:Boolean):void { + _touchable = value; + } + + public function get animation():String { + return ""; + } + + public function get inverted():Boolean { + return false; + } + + public function get offsetX():Number { + return 0; + } + + public function get offsetY():Number { + return 0; + } + + public function get registration():String { + return "topLeft"; + } + + public function get art():ICitrusArt { + return null; + } + + public function handleArtReady(citrusArt:ICitrusArt):void { + } + + public function handleArtChanged(citrusArt:ICitrusArt):void { + } + + + } + +} \ No newline at end of file diff --git a/src/citrus/sounds/SoundManager.as b/src/citrus/sounds/SoundManager.as new file mode 100644 index 00000000..e2ad946f --- /dev/null +++ b/src/citrus/sounds/SoundManager.as @@ -0,0 +1,443 @@ +package citrus.sounds { + + import aze.motion.eaze; + + import citrus.core.citrus_internal; + + import citrus.events.CitrusEventDispatcher; + import citrus.events.CitrusSoundEvent; + + import flash.media.SoundMixer; + import flash.media.SoundTransform; + import flash.utils.Dictionary; + + public class SoundManager extends CitrusEventDispatcher { + + internal static var _instance:SoundManager; + + protected var soundsDic:Dictionary; + protected var soundGroups:Vector.; + + protected var _masterVolume:Number = 1; + protected var _masterMute:Boolean = false; + + public function SoundManager() { + + soundsDic = new Dictionary(); + soundGroups = new Vector.(); + + //default groups + createGroup(CitrusSoundGroup.BGM); + createGroup(CitrusSoundGroup.SFX); + createGroup(CitrusSoundGroup.UI); + + addEventListener(CitrusSoundEvent.SOUND_LOADED, handleSoundLoaded); + + } + + public static function getInstance():SoundManager { + if (!_instance) + _instance = new SoundManager(); + + return _instance; + } + + public function destroy():void { + var csg:CitrusSoundGroup; + for each(csg in soundGroups) + csg.destroy(); + + var s:CitrusSound; + for each(s in soundsDic) + s.destroy(); + + soundsDic = null; + _instance = null; + + removeEventListeners(); + } + + /** + * Register a new sound an initialize its values with the params objects. Accepted parameters are: + *
    • sound : a url, a class or a Sound object.
    • + *
    • volume : the initial volume. the real final volume is calculated like so : volume x group volume x master volume.
    • + *
    • panning : value between -1 and 1 - unaffected by group or master.
    • + *
    • mute : default false, whether to start of muted or not.
    • + *
    • loops : default 0 (plays once) . -1 will loop infinitely using Sound.play(0,int.MAX_VALUE) and a positive value will use an event based looping system and events will be triggered from CitrusSoundInstance when sound complete and loops back
    • + *
    • permanent : by default set to false. if set to true, this sound cannot be forced to be stopped to leave room for other sounds (if for example flash soundChannels are not available) and cannot be played more than once . By default sounds can be forced to stop, that's good for sound effects. You would want your background music to be set as permanent.
    • + *
    • group : the groupID of a group, no groups are set by default. default groups ID's are CitrusSoundGroup.SFX (sound effects) and CitrusSoundGroup.BGM (background music)
    • + *
    + */ + public function addSound(id:String, params:Object = null):void { + if (!params.hasOwnProperty("sound")) + throw new Error("SoundManager addSound() sound:"+id+"can't be added with no sound definition in the params."); + if (id in soundsDic) + trace(this, id, "already exists."); + else + soundsDic[id] = new CitrusSound(id, params); + } + + /** + * add your own custom CitrusSoundGroup here. + */ + public function addGroup(group:CitrusSoundGroup):CitrusSoundGroup + { + soundGroups.push(group); + return group; + } + + /** + * create a CitrusSoundGroup with a group id. + */ + public function createGroup(groupID:String):CitrusSoundGroup + { + var group:CitrusSoundGroup; + + for each(var sg:CitrusSoundGroup in soundGroups) + if (sg.groupID == groupID) + group = sg; + + if (group != null) + { + trace("Sound Manager : trying to create group ", groupID, " but it already exists."); + return group; + } + + group = new CitrusSoundGroup(); + group.citrus_internal::setGroupID(groupID); + soundGroups.push(group); + return group; + } + + /** + * removes a group and detaches all its sounds - they will now have their default volume modulated only by masterVolume + */ + public function removeGroup(groupID:String):void + { + var g:CitrusSoundGroup = getGroup(groupID); + var i:int = soundGroups.lastIndexOf(g); + if ( i > -1) + { + soundGroups.splice(i, 1); + g.destroy(); + } + else + trace("Sound Manager : group", groupID, "not found for removal."); + } + + /** + * moves a sound to a group - if groupID is null, sound is simply removed from any groups + * @param soundName + * @param groupID ("BGM", "SFX" or custom group id's) + */ + public function moveSoundToGroup(soundName:String, groupID:String = null):void + { + var s:CitrusSound; + var g:CitrusSoundGroup; + if (soundName in soundsDic) + { + s = soundsDic[soundName]; + if (s.group != null) + s.group.removeSound(s); + if(groupID != null) + g = getGroup(groupID) + if (g) + g.addSound(s); + } + else + trace(this,"moveSoundToGroup() : sound",soundName,"doesn't exist."); + } + + /** + * return group of id 'name' , defaults would be SFX or BGM + * @param name + * @return CitrusSoundGroup + */ + public function getGroup(name:String):CitrusSoundGroup + { + var sg:CitrusSoundGroup; + for each(sg in soundGroups) + { + if (sg.groupID == name) + return sg; + } + trace(this,"getGroup() : group",name,"doesn't exist."); + return null; + } + + /** + * returns a CitrusSound object. you can use this reference to change volume/panning/mute or play/pause/resume/stop sounds without going through SoundManager's methods. + */ + public function getSound(name:String):CitrusSound + { + if (name in soundsDic) + return soundsDic[name]; + else + trace(this,"getSound() : sound",name,"doesn't exist."); + return null; + } + + public function preloadAllSounds():void + { + var cs:CitrusSound; + for each (cs in soundsDic) + cs.load(); + } + + /** + * pauses all playing sounds + * @param except list of sound names to not pause. + */ + public function pauseAll(...except):void + { + loop1:for each(var cs:CitrusSound in soundsDic) { + for each (var soundToPreserve:String in except) + if (soundToPreserve == cs.name) + continue loop1; + cs.pause(); + } + } + + /** + * resumes all paused sounds + * @param except list of sound names to not resume. + */ + public function resumeAll(...except):void + { + loop1:for each(var cs:CitrusSound in soundsDic) { + for each (var soundToPreserve:String in except) + if (soundToPreserve == cs.name) + continue loop1; + cs.resume(); + } + } + + public function playSound(id:String):CitrusSoundInstance { + if (id in soundsDic) + return CitrusSound(soundsDic[id]).play(); + else + trace(this, "playSound() : sound", id, "doesn't exist."); + return null; + } + + public function pauseSound(id:String):void { + if (id in soundsDic) + CitrusSound(soundsDic[id]).pause(); + else + trace(this,"pauseSound() : sound",id,"doesn't exist."); + } + + public function resumeSound(id:String):void { + if (id in soundsDic) + CitrusSound(soundsDic[id]).resume(); + else + trace(this,"resumeSound() : sound",id,"doesn't exist."); + } + + public function stopSound(id:String):void { + if (id in soundsDic) + CitrusSound(soundsDic[id]).stop(); + else + trace(this,"stopSound() : sound",id,"doesn't exist."); + } + + public function removeSound(id:String):void { + stopSound(id); + if (id in soundsDic) + { + CitrusSound(soundsDic[id]).destroy(); + soundsDic[id] = null; + delete soundsDic[id]; + } + else + trace(this,"removeSound() : sound",id,"doesn't exist."); + } + + public function soundIsPlaying(sound:String):Boolean + { + if (sound in soundsDic) + return CitrusSound(soundsDic[sound]).isPlaying; + else + trace(this, "soundIsPlaying() : sound", sound, "doesn't exist."); + return false; + } + + public function soundIsPaused(sound:String):Boolean + { + if (sound in soundsDic) + return CitrusSound(soundsDic[sound]).isPaused; + else + trace(this, "soundIsPaused() : sound", sound, "doesn't exist."); + return false; + } + + public function removeAllSounds(...except):void { + + loop1:for each(var cs:CitrusSound in soundsDic) { + for each (var soundToPreserve:String in except) + if (soundToPreserve == cs.name) + continue loop1; + removeSound(cs.name); + } + } + + public function get masterVolume():Number + { + return _masterVolume; + } + + public function get masterMute():Boolean + { + return _masterMute; + } + + /** + * sets the master volume : resets all sound transforms to masterVolume*groupVolume*soundVolume + */ + public function set masterVolume(val:Number):void + { + var tm:Number = _masterVolume; + if (val >= 0 && val <= 1) + _masterVolume = val; + else + _masterVolume = 1; + + if (tm != _masterVolume) + { + var s:String; + for (s in soundsDic) + soundsDic[s].resetSoundTransform(true); + } + } + + /** + * sets the master mute : resets all sound transforms to volume 0 if true, or + * returns to normal volue if false : normal volume is masterVolume*groupVolume*soundVolume + */ + public function set masterMute(val:Boolean):void + { + if (val != _masterMute) + { + _masterMute = val; + var s:String; + for (s in soundsDic) + soundsDic[s].resetSoundTransform(true); + } + } + + /** + * tells if the sound is added in the list. + * @param id + * @return + */ + public function soundIsAdded(id:String):Boolean { + return (id in soundsDic); + } + + /** + * Mute/unmute Flash' SoundMixer. No sound will be heard but they're still playing. + */ + public function muteFlashSound(mute:Boolean = true):void { + + var s:SoundTransform = SoundMixer.soundTransform; + s.volume = mute ? 0 : 1; + SoundMixer.soundTransform = s; + } + + /** + * Return true if Flash' SoundMixer is muted. + */ + public function isFlashSoundMuted():Boolean { + + return SoundMixer.soundTransform.volume == 0; + } + + /** + * set volume of an individual sound (its group volume and the master volume will be multiplied to it to get the final volume) + */ + public function setVolume(id:String, volume:Number):void { + if (id in soundsDic) + soundsDic[id].volume = volume; + else + trace(this, "setVolume() : sound", id, "doesn't exist."); + } + + /** + * set pan of an individual sound (not affected by group or master + */ + public function setPanning(id:String, panning:Number):void { + if (id in soundsDic) + soundsDic[id].panning = panning; + else + trace(this, "setPanning() : sound", id, "doesn't exist."); + } + + /** + * set mute of a sound : if set to mute, neither the group nor the master volume will affect this sound of course. + */ + public function setMute(id:String, mute:Boolean):void { + if (id in soundsDic) + soundsDic[id].mute = mute; + else + trace(this, "setMute() : sound", id, "doesn't exist."); + } + + /** + * Stop playing all the current sounds. + * @param except an array of soundIDs you want to preserve. + */ + public function stopAllPlayingSounds(...except):void { + + loop1:for each(var cs:CitrusSound in soundsDic) { + for each (var soundToPreserve:String in except) + if (soundToPreserve == cs.name) + continue loop1; + stopSound(cs.name); + } + } + + /** + * tween the volume of a CitrusSound. If callback is defined, its optional argument will be the CitrusSound. + * @param id + * @param volume + * @param tweenDuration + * @param callback + */ + public function tweenVolume(id:String, volume:Number = 0, tweenDuration:Number = 2, callback:Function = null):void { + if (soundIsPlaying(id)) { + + var citrusSound:CitrusSound = CitrusSound(soundsDic[id]); + var tweenvolObject:Object = {volume:citrusSound.volume}; + + eaze(tweenvolObject).to(tweenDuration, {volume:volume}) + .onUpdate(function():void { + citrusSound.volume = tweenvolObject.volume; + }).onComplete(function():void + { + + if (callback != null) + if (callback.length == 0) + callback(); + else + callback(citrusSound); + }); + } else + trace("the sound " + id + " is not playing"); + } + + public function crossFade(fadeOutId:String, fadeInId:String, tweenDuration:Number = 2):void { + + tweenVolume(fadeOutId, 0, tweenDuration); + tweenVolume(fadeInId, 1, tweenDuration); + } + + protected function handleSoundLoaded(e:CitrusSoundEvent):void + { + var cs:CitrusSound; + for each(cs in soundsDic) + if (!cs.loaded) + return; + dispatchEvent(new CitrusSoundEvent(CitrusSoundEvent.ALL_SOUNDS_LOADED, e.sound,null)); + } + } +} diff --git a/src/citrus/utils/AGameData.as b/src/citrus/utils/AGameData.as new file mode 100644 index 00000000..750ef3b7 --- /dev/null +++ b/src/citrus/utils/AGameData.as @@ -0,0 +1,141 @@ +package citrus.utils +{ + + import flash.utils.Dictionary; + import flash.utils.flash_proxy; + import flash.utils.Proxy; + import org.osflash.signals.Signal; + + /** + * This is an (optional) abstract class to store your game's data such as lives, score, levels or even complex objects... + * identified by strings. + * + * the dataChanged signal is dispatched when any property changes with its name and value as arguments. + * + * if typeVerification is set to true, you will get an error thrown when you try to change a property with a value of different type. + * + * you can extend AGameData to synchronize your data with a shared object or a server for example + * (keep operations on shared objects/server to a strict minimum by "flushing" and "reading" values from them only + * when necessary...) + * or simply extend it to setup initial values in your custom AGameData constructor. + */ + dynamic public class AGameData extends Proxy + { + + /** + * dispatched when a property is defined or changed. + */ + public var dataChanged:Signal; + + /** + * throw an argument error when trying to change a property with a value of a different type. + */ + public var typeVerification:Boolean = true; + + private var __dict:Dictionary; + private var __propNames:Vector.; + private var __numProps:int; + + public function AGameData() + { + __dict = new Dictionary(); + __propNames = new Vector.(); + + dataChanged = new Signal(String, Object); + } + + override flash_proxy function callProperty(methodName:*, ... args):* + { + if (__dict[methodName] is Function) + return __dict[methodName].apply(this, args); + return undefined; + } + + override flash_proxy function getDescendants(name:*):* + { + return __dict[name]; + } + + override flash_proxy function isAttribute(name:*):Boolean + { + return name in __dict; + } + + override flash_proxy function nextName(index:int):String + { + return __propNames[index - 1]; + } + + override flash_proxy function nextNameIndex(index:int):int + { + if (index == 0) + { + var propNames:Vector. = __propNames; + propNames.length = 0; + var size:int; + for (var k:*in __dict) + { + propNames[size++] = k; + } + __numProps = size; + } + + return (index < __numProps) ? (index + 1) : 0; + } + + override flash_proxy function nextValue(index:int):* + { + return __dict[__propNames[index - 1]]; + } + + override flash_proxy function deleteProperty(name:*): Boolean + { + var ret:Boolean = (name in __dict); + delete __dict[name]; + return ret; + } + + override flash_proxy function getProperty(name:*):* + { + if (__dict[name] != undefined) + return __dict[name]; + + throw new ArgumentError("[AGameData] property " + name + " doesn't exist."); + } + + override flash_proxy function hasProperty(name:*):Boolean + { + return __dict[name] != undefined; + } + + override flash_proxy function setProperty(name:*, value:*):void + { + if (__dict[name] != undefined) + { + if (typeVerification) + { + var type1:Class = value.constructor; + var type2:Class = __dict[name].constructor; + if (!(type1 === type2)) + throw new ArgumentError("[AGameData] you're trying to set '" + name + "'s value of type " + type2 + " to a new value of type " + type1); + } + + if (value === __dict[name]) + return; + + __dict[name] = value; + } + else + __dict[name] = value; + + dataChanged.dispatch(String(name), value); + } + + public function destroy():void + { + __dict = null; + __propNames.length = 0; + dataChanged.removeAll(); + } + } +} diff --git a/src/citrus/utils/LevelManager.as b/src/citrus/utils/LevelManager.as new file mode 100644 index 00000000..3536cd93 --- /dev/null +++ b/src/citrus/utils/LevelManager.as @@ -0,0 +1,217 @@ +package citrus.utils { + + import org.osflash.signals.Signal; + + import flash.display.Loader; + import flash.events.Event; + import flash.events.IOErrorEvent; + import flash.net.URLLoader; + import flash.net.URLRequest; + import flash.system.ApplicationDomain; + import flash.system.LoaderContext; + import flash.system.SecurityDomain; + + /** + * The LevelManager is a complex but powerful class, you can use simple states for levels with SWC/SWF/XML. + * + *

    Before using it, be sure that you have good OOP knowledge. For using it, you must use an Abstract state class + * that you give as constructor parameter : Alevel.

    + * + *

    The six ways to set up your level : + *

      + *
    • levelManager.levels = [Level1, Level2];
    • + *
    • levelManager.levels = [[Level1, "level1.swf"], [level2, "level2.swf"]];
    • + *
    • levelManager.levels = [[Level1, "level1.xml"], [level2, "level2.xml"]];
    • + *
    • levelManager.levels = [[Level1, level1XMLVar], [level2, level2XMLVar]];
    • + *
    • levelManager.levels = [[Level1, XML(new level1XMLEmbed())], [level2, XML(new level2XMLEmbed())]];
    • + *
    • levelManager.levels = [[Level1, Level1_SWC], [level2, Level2_SWC]];
    • + *

    + * + *

    An instantiation example in your Main class (you may also use the AGameData to store your levels) : + * levelManager = new LevelManager(ALevel); + * levelManager.onLevelChanged.add(_onLevelChanged); + * levelManager.levels = [Level1, Level2]; + * levelManager.gotoLevel();

    + * + *

    The _onLevelChanged function gives in parameter the Alevel that you associate to your state : state = lvl; + * Then you can associate other functions : + *

      + *
    • lvl.lvlEnded.add(_nextLevel);
    • + *
    • lvl.restartLevel.add(_restartLevel);
    • + *
    + * And their respective actions : + *
      + *
    • _levelManager.nextLevel();
    • + *
    • state = _levelManager.currentLevel as IState;
    • + *

    + * + *

    The ALevel class must implement public var lvlEnded and restartLevel Signals in its constructor. + * If you have associated a SWF or SWC file to your level, you must add a flash MovieClip as a parameter into its constructor, + * or a XML if it is one!

    + */ + public class LevelManager { + + static private var _instance:LevelManager; + + public var onLevelChanged:Signal; + + public var checkPolicyFile:Boolean = false; + + /** + * If you want to load your SWF level on iOS, set it to ApplicationDomain.currentDomain. + */ + public var applicationDomain:ApplicationDomain = null; + public var securityDomain:SecurityDomain = null; + + public var levels:Array; + public var currentLevel:Object; + + /** + * If set to true, and the level comes from an SWF, the SWF is only loaded once, then cached. + * Enable this if you plan to deliver an IOS app, since IOS does not support SWF reloading + * in AOT (build) mode. + */ + public var enableSwfCaching:Boolean = false; + + private var _ALevel:Class; + private var _currentIndex:uint; + private var _levelData:Array; + + public function LevelManager(ALevel:Class) { + + _instance = this; + + _ALevel = ALevel; + _levelData = new Array(); + + onLevelChanged = new Signal(_ALevel); + _currentIndex = 0; + } + + static public function getInstance():LevelManager { + return _instance; + } + + + public function destroy():void { + + onLevelChanged.removeAll(); + + currentLevel = null; + } + + public function nextLevel():void { + + if (_currentIndex < levels.length - 1) { + ++_currentIndex; + } + + gotoLevel(); + } + + public function prevLevel():void { + + if (_currentIndex > 0) { + --_currentIndex; + } + + gotoLevel(); + } + + /** + * Call the LevelManager instance's gotoLevel() function to launch your first level, or you may specify it. + * @param index the level index from 1 to ... ; different from the levels' array indexes. + */ + public function gotoLevel(index:uint = 0):void { + + if (index != 0) + _currentIndex = index - 1; + + // Level SWF and SWC are undefined + if (levels[_currentIndex][0] == undefined) { + + currentLevel = _ALevel(new levels[_currentIndex]); + + onLevelChanged.dispatch(currentLevel); + + // It's a SWC or a XML ? + } else if (levels[_currentIndex][1] is Class || levels[_currentIndex][1] is XML) { + + currentLevel = (levels[_currentIndex][1] is Class) ? _ALevel(new levels[_currentIndex][0](new levels[_currentIndex][1]())) : _ALevel(new levels[_currentIndex][0](levels[_currentIndex][1])); + + onLevelChanged.dispatch(currentLevel); + + // So it's an external SWF or XML, we load it + } else { + + var isXml:String = levels[_currentIndex][1].substring(levels[_currentIndex][1].length - 4).toLowerCase(); + if (isXml == ".xml" || isXml == ".lev" || isXml == ".tmx") { + + var urlLoader:URLLoader = new URLLoader(); + urlLoader.load(new URLRequest(levels[_currentIndex][1])); + urlLoader.addEventListener(Event.COMPLETE, _levelLoaded); + + } else { + + if (enableSwfCaching && _levelData.length > _currentIndex && _levelData[_currentIndex] != null) { + // Use already loaded (cached) SWF content: + createLevelFromCache(); + } else { + // load SWF from file: + var loader:Loader = new Loader(); + var loaderContext:LoaderContext = new LoaderContext(checkPolicyFile, applicationDomain, securityDomain); + loader.load(new URLRequest(levels[_currentIndex][1]), loaderContext); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _levelLoaded); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, _handleLoaderError); + } + } + } + } + + private function _levelLoaded(evt:Event):void { + if (evt.target is URLLoader) { + currentLevel = _ALevel(new levels[_currentIndex][0](XML(evt.target.data))); + } else { + if (enableSwfCaching) { + _levelData[_currentIndex] = evt.target.loader.content; + } + + currentLevel = _ALevel(new levels[_currentIndex][0](evt.target.loader.content)); + } + + onLevelChanged.dispatch(currentLevel); + + if (evt.target is Loader) { + + evt.target.contentLoaderInfo.removeEventListener(Event.COMPLETE, _levelLoaded); + evt.target.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, _handleLoaderError); + evt.target.loader.unloadAndStop(); + + } else if (evt.target is URLLoader) { + evt.target.removeEventListener(Event.COMPLETE, _levelLoaded); + } + } + + /** + * Creates a level form a cached object. Used when enableSwfCache is set to true, + * to prevent SWF-reloading, which is not possible on IOS builds (AOT mode). + */ + private function createLevelFromCache():void { + currentLevel = _ALevel(new levels[_currentIndex][0](_levelData[_currentIndex])); + onLevelChanged.dispatch(currentLevel); + } + + private function _handleLoaderError(evt:IOErrorEvent):void { + trace(evt.type + " - " + evt.text); + } + + public function get nameCurrentLevel():String { + return currentLevel.nameLevel; + } + + public function get currentIndex():uint + { + return _currentIndex; + } + } +} \ No newline at end of file diff --git a/src/citrus/utils/LoadManager.as b/src/citrus/utils/LoadManager.as new file mode 100644 index 00000000..0a2c2621 --- /dev/null +++ b/src/citrus/utils/LoadManager.as @@ -0,0 +1,164 @@ +package citrus.utils { + + import citrus.core.CitrusObject; + import citrus.view.ICitrusArt; + import org.osflash.signals.Signal; + + import flash.display.Loader; + import flash.display.Sprite; + import flash.events.Event; + import flash.events.IOErrorEvent; + import flash.events.ProgressEvent; + import flash.utils.Dictionary; + + /** + * The load manager keeps track of the loading status of Loader objects, returning an overall value for all graphics + * that are being loaded. This class is necessary when loading level graphics at runtime and finding out when all the graphics + * are finished loading. The LoadManager instance can be accessed via the state's CitrusView object. + * There is a LoadManager for each view state. + */ + public class LoadManager { + + public var onLoaded:Signal; + public var onLoadComplete:Signal; + + private var _bytesLoaded:Dictionary; + private var _bytesTotal:Dictionary; + private var _objects:Dictionary; + private var _numLoadersLoading:Number = 0; + + /** + * Creates a new LoadManager instance. The CitrusView does this for you. You can access the created LoadManager view the + * CitrusView object. + */ + public function LoadManager() { + + onLoaded = new Signal(CitrusObject,ICitrusArt); + onLoadComplete = new Signal(); + } + + public function destroy():void { + + onLoaded.removeAll(); + onLoadComplete.removeAll(); + } + + /** + * Returns the sum of all the bytes that have been loaded by the current view. + */ + public function get bytesLoaded():Number { + + var bytesLoaded:Number = 0; + var bytes:Number; + for each (bytes in _bytesLoaded) + bytesLoaded += bytes; + + return bytesLoaded; + } + + /** + * Returns the sum of all the bytes that will need to be loaded by the current view. + */ + public function get bytesTotal():Number { + + var bytesTotal:Number = 0; + var bytes:Number; + for each (bytes in _bytesTotal) + bytesTotal += bytes; + + return bytesTotal; + } + + /** + * The CitrusView calls this method on all graphics objects that it creates to monitor its load progress. + * It passes any object into the add() method, and it will recurse through it and search for any loaders on the object. + * If/when it finds a loader (or if it IS a loader), it will add it to the list of Loaders that it is monitoring. + * If you use Starling view, it can't be recursive, so we check if the StarlingArt's loader is defined. + * @param potentialLoader The object that needs load monitoring. + * @param recursionDepth How many child objects the add() method should recurse through before giving up searching for a Loader object. + * @return Whether or not it found a loader object. + */ + public function add(potentialLoader:*,object:CitrusObject, recursionDepth:Number = 1):Boolean { + + var loader:Loader; + + if (potentialLoader is Loader || potentialLoader.loader) { + + // We found our first loader, so reset the bytesLoaded/Total dictionaries to get a fresh count. + if (_numLoadersLoading == 0) { + _bytesLoaded = new Dictionary(); + _bytesTotal = new Dictionary(); + _objects = new Dictionary(); + } + + _numLoadersLoading++; + loader = (potentialLoader is Loader) ? potentialLoader as Loader : potentialLoader.loader as Loader; + loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, handleLoaderProgress); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleLoaderComplete); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, handleLoaderError); + _bytesLoaded[loader] = 0; + _bytesTotal[loader] = 0; + _objects[loader] = {co:object,art:potentialLoader}; + + return true; + + } else if (potentialLoader is flash.display.Sprite) { + + var searchDepth:Number = recursionDepth - 1; + var n:Number = flash.display.Sprite(potentialLoader).numChildren; + + for (var i:int = 0; i < n; i++) { + var found:Boolean = add(flash.display.Sprite(potentialLoader).getChildAt(i),object, searchDepth); + if (found) + return true; + } + + return false; + + } + + return false; + } + + private function handleLoaderProgress(e:ProgressEvent):void { + _bytesLoaded[e.target.loader] = e.bytesLoaded; + _bytesTotal[e.target.loader] = e.bytesTotal; + } + + private function handleLoaderComplete(e:Event):void { + + var citrusObject:CitrusObject = _objects[e.target.loader].co; + var art:ICitrusArt = _objects[e.target.loader].art; + + onLoaded.dispatch(citrusObject,_objects[e.target.loader].art as ICitrusArt); + + clearLoader(e.target.loader); + + if (_numLoadersLoading == 0) + onLoadComplete.dispatch(); + } + + private function handleLoaderError(e:IOErrorEvent):void { + + clearLoader(e.target.loader); + + if (_numLoadersLoading == 0) + onLoadComplete.dispatch(); + // TODO Make this error more robust. + trace("Warning: Art loading error in current state: " + e.text); + } + + private function clearLoader(loader:Loader):void + { + loader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS, handleLoaderProgress); + loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, handleLoaderComplete); + loader.contentLoaderInfo.removeEventListener(IOErrorEvent.IO_ERROR, handleLoaderError); + + _numLoadersLoading--; + + delete _bytesTotal[loader]; + delete _bytesTotal[loader]; + delete _objects[loader]; + } + } +} \ No newline at end of file diff --git a/src/citrus/utils/Mobile.as b/src/citrus/utils/Mobile.as new file mode 100644 index 00000000..ba3f5916 --- /dev/null +++ b/src/citrus/utils/Mobile.as @@ -0,0 +1,123 @@ +package citrus.utils { + + import citrus.core.CitrusEngine; + + import flash.display.Stage; + import flash.system.Capabilities; + + /** + * This class provides mobile devices information. + */ + public class Mobile { + + static private var _STAGE:Stage; + + static private const _IOS_MARGIN:uint = 40; + + static private const _IPHONE_RETINA_WIDTH:uint = 640; + static private const _IPHONE_RETINA_HEIGHT:uint = 960; + static private const _IPHONE5_RETINA_HEIGHT:uint = 1136; + + static private const _IPAD_WIDTH:uint = 768; + static private const _IPAD_HEIGHT:uint = 1024; + static private const _IPAD_RETINA_WIDTH:uint = 1536; + static private const _IPAD_RETINA_HEIGHT:uint = 2048; + + public function Mobile() { + + } + + static public function isIOS():Boolean { + return (Capabilities.version.substr(0, 3) == "IOS"); + } + + static public function isAndroid():Boolean { + return (Capabilities.version.substr(0, 3) == "AND"); + } + + static public function isLandscapeMode():Boolean { + + if (!_STAGE) + _STAGE = CitrusEngine.getInstance().stage; + + return (_STAGE.fullScreenWidth > _STAGE.fullScreenHeight); + } + + static public function isRetina():Boolean { + + if (Mobile.isIOS()) { + + if (!_STAGE) + _STAGE = CitrusEngine.getInstance().stage; + + if (isLandscapeMode()) + return (_STAGE.fullScreenWidth == _IPHONE_RETINA_HEIGHT || _STAGE.fullScreenWidth == _IPHONE5_RETINA_HEIGHT || _STAGE.fullScreenWidth == _IPAD_RETINA_HEIGHT || _STAGE.fullScreenHeight == _IPHONE_RETINA_HEIGHT || _STAGE.fullScreenHeight == _IPHONE5_RETINA_HEIGHT || _STAGE.fullScreenHeight == _IPAD_RETINA_HEIGHT); + else + return (_STAGE.fullScreenWidth == _IPHONE_RETINA_WIDTH || _STAGE.fullScreenWidth == _IPAD_RETINA_WIDTH || _STAGE.fullScreenHeight == _IPHONE_RETINA_WIDTH || _STAGE.fullScreenHeight == _IPAD_RETINA_WIDTH); + + } else + return false; + } + + static public function isIpad():Boolean { + + if (Mobile.isIOS()) { + + if (!_STAGE) + _STAGE = CitrusEngine.getInstance().stage; + + if (isLandscapeMode()) + return (_STAGE.fullScreenWidth == _IPAD_HEIGHT || _STAGE.fullScreenWidth == _IPAD_RETINA_HEIGHT || _STAGE.fullScreenHeight == _IPAD_HEIGHT || _STAGE.fullScreenHeight == _IPAD_RETINA_HEIGHT); + else + return (_STAGE.fullScreenWidth == _IPAD_WIDTH || _STAGE.fullScreenWidth == _IPAD_RETINA_WIDTH || _STAGE.fullScreenHeight == _IPAD_WIDTH || _STAGE.fullScreenHeight == _IPAD_RETINA_WIDTH); + + } else + return false; + } + + static public function isIphone5():Boolean { + + if (Mobile.isIOS()) { + + if (!_STAGE) + _STAGE = CitrusEngine.getInstance().stage; + + return (_STAGE.fullScreenHeight == _IPHONE5_RETINA_HEIGHT || _STAGE.fullScreenHeight == Mobile._IPHONE5_RETINA_HEIGHT - _IOS_MARGIN); + + } else + return false; + } + + static public function get iOS_MARGIN():uint { + return _IOS_MARGIN; + } + + static public function get iPHONE_RETINA_WIDTH():uint { + return _IPHONE_RETINA_WIDTH; + } + + static public function get iPHONE_RETINA_HEIGHT():uint { + return _IPHONE_RETINA_HEIGHT; + } + + static public function get iPHONE5_RETINA_HEIGHT():uint { + return _IPHONE5_RETINA_HEIGHT; + } + + static public function get iPAD_WIDTH():uint { + return _IPAD_WIDTH; + } + + static public function get iPAD_HEIGHT():uint { + return _IPAD_HEIGHT; + } + + static public function get iPAD_RETINA_WIDTH():uint { + return _IPAD_RETINA_WIDTH; + } + + static public function get iPAD_RETINA_HEIGHT():uint { + return _IPAD_RETINA_HEIGHT; + } + } +} diff --git a/src/citrus/utils/Platform.as b/src/citrus/utils/Platform.as new file mode 100644 index 00000000..f17e31d6 --- /dev/null +++ b/src/citrus/utils/Platform.as @@ -0,0 +1,380 @@ +package citrus.utils { + + import citrus.core.CitrusEngine; + import flash.display.Stage; + import flash.system.Capabilities; + + /** + * Last updated: March 27th 2015 by @SnkyGames + * This class provides platform information. + * + * Do not use Capabilities.os or Capabilities.manufacturer to determine a capability based on the operating system + * Different launch images can be displayed on an iPad and iPhone 6 plus, based on their orientation, at the time of application launch. + * There are over 11,000 unique Android devices, need more consts GUYS.. + * + * + * + * Some interesting links? + * + * http://blogs.adobe.com/airodynamics/2015/03/09/launch-images-on-ios-with-adobe-air/ + * http://forum.starling-framework.org/topic/iphone-6-question + * http://qz.com/109657/here-are-the-11868-devices-and-counting-that-every-android-app-has-to-work-on/ + * http://carl-topham.com/theblog/post/cross-platform-flash-as3-cd-rom-part-1/ + * http://ivomynttinen.com/blog/the-ios-7-design-cheat-sheet/ + * http://jacksondunstan.com/articles/2596#more-2596 + * http://forum.starling-framework.org/topic/detect-device-modelperformance/page/2 ( sigh, so many.. different ways ) + * + */ + + public class Platform { + + static private const _PLAYER_VERSION:String = Capabilities.version.substr( 0 , 3 ); + static private const _PLAYER_TYPE:String = Capabilities.playerType; // "Desktop" == air-runtime? , "StandAlone" == ? , "PlugIn" == browser, "ActiveX" == browser + + //TIP: ( iOS Status Bar ) Height + static private const _IOS_LEGACY_STATUSBAR_HEIGHT:uint = 20; + static private const _IOS_RETINA_STATUSBAR_HEIGHT:uint = 40; + + //TIP: ( 2G , 3G , 3GS ) Portrait: Default~iphone.png + static private const _IPHONE_LEGACY_WIDTH:uint = 320; + static private const _IPHONE_LEGACY_HEIGHT:uint = 480; + + //TIP: ( 4 / 4S ) Portrait: Default@2x~iphone.png + static private const _IPHONE_RETINA_FOUR_WIDTH:uint = 640; + static private const _IPHONE_RETINA_FOUR_HEIGHT:uint = 960; + + //TIP: ( 5 , 5C , 5S , iPOD Touch 5g ) Portrait: Default-568h@2x~iphone.png + static private const _IPHONE_RETINA_FIVE_WIDTH:uint = 640; + static private const _IPHONE_RETINA_FIVE_HEIGHT:uint = 1136; + + //TIP: ( 6 , 6 zoom ) Portrait: Default-375w-667h@2x~iphone.png + static private const _IPHONE_RETINA_SIX_WIDTH:uint = 750; + static private const _IPHONE_RETINA_SIX_HEIGHT:uint = 1334; + + //TIP: ( 6+ , 6+ zoom ) Portrait: Default-414w-736h@3x~iphone.png | Landscape: Default-Landscape-414w-736h@3x~iphone.png + static private const _IPHONE_RETINA_SIX_PLUS_WIDTH:uint = 1242; + static private const _IPHONE_RETINA_SIX_PLUS_HEIGHT:uint = 2208; + + //TIP: ( 1 / 2 / mini ) Portrait: Default-Portrait~ipad.png | Upside down Portrait: Default-PortraitUpsideDown~ipad.png | Left Landscape: Default-Landscape~ipad.png | Right Landscape: Default-LandscapeRight~ipad.png + static private const _IPAD_LEGACY_WIDTH:uint = 768; + static private const _IPAD_LEGACY_HEIGHT:uint = 1024; + + //TIP: ( 3 / 4 / mini 2 / mini 3 / air / air 2 ) Portrait: Default-Portrait@2x~ipad.png | Upside down Portrait: Default-PortraitUpsideDown@2x~ipad.png | Left Landscape: Default-LandscapeLeft@2x~ipad.png | Right Landscape: Default-LandscapeRight@2x~ipad.png + static private const _IPAD_RETINA_WIDTH:uint = 1536; + static private const _IPAD_RETINA_HEIGHT:uint = 2048; + + static private var _STAGE:Stage; + static private var _PLATFORM_IS_BROWSER:uint = 2; + static private var _PLATFORM_IS_DESKTOP:uint = 2; + static private var _PLATFORM_IS_IOS:uint = 2; + static private var _PLATFORM_IS_AND:uint = 2; + + static private var _PLATFORM_SPECIFICS_IS_IPAD:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_IPAD_LEGACY:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_IPAD_RETINA:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_IPHONE:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_IPHONE_LEGACY:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_IPHONE_FOUR:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_IPHONE_FIVE:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_IPHONE_SIX:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_IPHONE_SIX_PLUS:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_WIN:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_MAC:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_LNX:uint = 2; + + static private var _PLATFORM_SPECIFICS_IS_RETINA:uint = 2; + static private var _PLATFORM_SPECIFICS_IS_LANDSCAPE:Boolean = false; //I used a bool.. oh noes >: + //static private var _PLATFORM_SPECIFICS_IS_IPHONE_ZOOMED:uint = 2; //I know, I know, I've gone overboard, gg apple, not sure if needed. :D + + + + + public function Platform(){ /*...*/ } + + ///PUBLIC FINISHED - these should be called outside of this class. - e.g. if ( Platform.isIphoneSixPlus() ) { .. } --------- 0 = false / 1 = true / 2 = not queried ( only queries once, tis why no bools, they take up the same memory space anyway ) + static public function isBrowser():uint { + return _PLATFORM_IS_BROWSER != 2 ? _PLATFORM_IS_BROWSER : queryBrowser(); + } + + static public function isDesktop():uint { + return _PLATFORM_IS_DESKTOP != 2 ? _PLATFORM_IS_DESKTOP : queryDesktop(); + } + + static public function isIOS():uint { + return _PLATFORM_IS_IOS != 2 ? _PLATFORM_IS_IOS : queryIOS(); + } + + static public function isAndroid():uint { + return _PLATFORM_IS_AND != 2 ? _PLATFORM_IS_AND : queryAndroid(); + } + + //more specific.. + static public function isWindows():uint { + return _PLATFORM_SPECIFICS_IS_WIN != 2 ? _PLATFORM_SPECIFICS_IS_WIN : queryWindows(); + } + + static public function isMac():uint { + return _PLATFORM_SPECIFICS_IS_MAC != 2 ? _PLATFORM_SPECIFICS_IS_MAC : queryMac(); + } + + static public function isLinux():uint { + return _PLATFORM_SPECIFICS_IS_LNX != 2 ? _PLATFORM_SPECIFICS_IS_LNX : queryLinux(); + } + + static public function isIphoneLegacy():uint { + return _PLATFORM_SPECIFICS_IS_IPHONE_LEGACY != 2 ? _PLATFORM_SPECIFICS_IS_IPHONE_LEGACY : queryIphoneLegacy(); + } + + static public function isIphoneFour():uint { + return _PLATFORM_SPECIFICS_IS_IPHONE_FOUR != 2 ? _PLATFORM_SPECIFICS_IS_IPHONE_FOUR : queryIphoneFour(); + } + + static public function isIphoneFive():uint { + return _PLATFORM_SPECIFICS_IS_IPHONE_FIVE != 2 ? _PLATFORM_SPECIFICS_IS_IPHONE_FIVE : queryIphoneFive(); + } + + static public function isIphoneSix():uint { + return _PLATFORM_SPECIFICS_IS_IPHONE_SIX != 2 ? _PLATFORM_SPECIFICS_IS_IPHONE_SIX : queryIphoneSix(); + } + + static public function isIphoneSixPlus():uint { + return _PLATFORM_SPECIFICS_IS_IPHONE_SIX_PLUS != 2 ? _PLATFORM_SPECIFICS_IS_IPHONE_SIX_PLUS : queryIphoneSixPlus(); + } + + static public function isIpadLegacy():uint { + return _PLATFORM_SPECIFICS_IS_IPAD_LEGACY != 2 ? _PLATFORM_SPECIFICS_IS_IPAD_LEGACY : queryIpadLegacy(); + } + + static public function isIpadRetina():uint { + return _PLATFORM_SPECIFICS_IS_IPAD_RETINA != 2 ? _PLATFORM_SPECIFICS_IS_IPAD_RETINA : queryIpadRetina(); + } + + static public function isIpad():uint { + return _PLATFORM_SPECIFICS_IS_IPAD != 2 ? _PLATFORM_SPECIFICS_IS_IPAD : ( isIpadLegacy() || isIpadRetina() ); + } + + static public function isIphone():uint { + return _PLATFORM_SPECIFICS_IS_IPHONE != 2 ? _PLATFORM_SPECIFICS_IS_IPHONE : ( isIOS() && uint( !isIpad() ) ); + } + + //extras + static public function isRetina():uint { + return _PLATFORM_SPECIFICS_IS_RETINA != 2 ? _PLATFORM_SPECIFICS_IS_RETINA : ( isIOS() && uint( !queryIphoneLegacy() ) && uint( !queryIpadLegacy() ) ); + } + + static private function isLandscape():Boolean { + //skipping the query way.. as this can't just be checked once, as the value could change. + + //stageValidate() - maybe a bit expensive/useless? here's why.. + //I could see isLandscape() being called in an update, this cannot be stored of course, as the value may change depending on the application being developed, + //although.. maybe it can be stored.. maybe CE / Starling has a 'handleOrientationChanged' function, that could modify isLandscape variable in this class, + //removing the need to: call 2 extra imports / call stageValidate() / even compare the width and the height ( at least in this class ). + stageValidate(); + _PLATFORM_SPECIFICS_IS_LANDSCAPE = ( _STAGE.fullScreenWidth > _STAGE.fullScreenHeight ); + return _PLATFORM_SPECIFICS_IS_LANDSCAPE; + } + + + static public function get iOS_STATUSBAR_HEIGHT():uint { + if ( !isIOS() ) + throw Error( "Not an iOS device!" ); + if ( isIpadLegacy() || isIphoneLegacy() ){ + return _IOS_LEGACY_STATUSBAR_HEIGHT; + } else if ( isIphone() || isIpad() ){ + return _IOS_RETINA_STATUSBAR_HEIGHT; + } else + throw Error( "Unknown / New iOS device, please update Platform.as" ); + } + + static public function get iPHONE_WIDTH():uint { + if ( !isIOS() ) + throw Error( "Not an iOS device!" ); + + if ( isIphoneLegacy() ) + return _IPHONE_LEGACY_WIDTH; + else if ( isIphoneFour() ) + return _IPHONE_RETINA_FOUR_WIDTH; + else if ( isIphoneFive() ) + return _IPHONE_RETINA_FIVE_WIDTH; + else if ( isIphoneSix() ) + return _IPHONE_RETINA_SIX_WIDTH; + else if ( isIphoneSixPlus() ) + return _IPHONE_RETINA_SIX_PLUS_WIDTH; + + throw Error( "Unknown / New iOS device, please update Platform.as" ); + } + + static public function get iPHONE_HEIGHT():uint { + if ( !isIOS() ) + throw Error( "Not an iOS device!" ); + + if ( isIphoneLegacy() ) + return _IPHONE_LEGACY_HEIGHT; + else if ( isIphoneFour() ) + return _IPHONE_RETINA_FOUR_HEIGHT; + else if ( isIphoneFive() ) + return _IPHONE_RETINA_FIVE_HEIGHT; + else if ( isIphoneSix() ) + return _IPHONE_RETINA_SIX_HEIGHT; + else if ( isIphoneSixPlus() ) + return _IPHONE_RETINA_SIX_PLUS_HEIGHT; + + throw Error( "Unknown / New iOS device, please update Platform.as" ); + } + + static public function get iPAD_WIDTH():uint { + if ( !isIOS() ) + throw Error( "Not an iOS device!" ); + + if ( isIpadLegacy() ) + return _IPAD_LEGACY_WIDTH; + else if ( isIpadRetina() ) + return _IPAD_RETINA_WIDTH; + else + throw Error( "Unknown / New iOS device, please update Platform.as" ); + } + + static public function get iPAD_HEIGHT():uint { + if ( !isIOS() ) + throw Error( "Not an iOS device!" ); + + if ( isIpadLegacy() ) + return _IPAD_LEGACY_HEIGHT; + else if ( isIpadRetina() ) + return _IPAD_RETINA_HEIGHT; + else + throw Error( "Unknown / New iOS device, please update Platform.as" ); + } + + ///PRIVATE FINISHED FUNCTIONS - unused publically, only ever called once, then stores results in the static vars. + static private function stageValidate():void { + var _ce:CitrusEngine = CitrusEngine.getInstance(); + if ( !_ce ) + throw Error( "Citrus Engine is null" ); + + _STAGE = _ce.stage; + if ( !_STAGE ) + throw Error( "Flash Stage is null.. uhm... guys? help.." ); + } + + static private function queryDesktop():uint { + _PLATFORM_SPECIFICS_IS_WIN = uint( _PLAYER_VERSION == "WIN" ); + _PLATFORM_SPECIFICS_IS_MAC = uint( _PLAYER_VERSION == "MAC" ); + _PLATFORM_SPECIFICS_IS_LNX = uint( _PLAYER_VERSION == "LNX" ); + return ( _PLATFORM_SPECIFICS_IS_WIN || _PLATFORM_SPECIFICS_IS_MAC || _PLATFORM_SPECIFICS_IS_LNX ); + } + + //specifics.. + static private function queryWindows():uint { + _PLATFORM_SPECIFICS_IS_WIN = uint( _PLAYER_VERSION == "WIN" ); + return _PLATFORM_SPECIFICS_IS_WIN; + } + + static private function queryMac():uint { + _PLATFORM_SPECIFICS_IS_MAC = uint( _PLAYER_VERSION == "MAC" ); + return _PLATFORM_SPECIFICS_IS_MAC; + } + + static private function queryLinux():uint { + _PLATFORM_SPECIFICS_IS_LNX = uint( _PLAYER_VERSION == "LNX" ); + return _PLATFORM_SPECIFICS_IS_LNX; + } + + static private function queryIphoneLegacy():uint { + if ( queryIOS() ){ + stageValidate(); + if ( isLandscape() ) + return ( uint( _STAGE.fullScreenWidth == _IPHONE_LEGACY_HEIGHT ) || uint( _STAGE.fullScreenWidth == ( _IPHONE_LEGACY_HEIGHT - _IOS_LEGACY_STATUSBAR_HEIGHT ) ) ); + else + return ( uint( _STAGE.fullScreenWidth == _IPHONE_LEGACY_WIDTH ) || uint( _STAGE.fullScreenWidth == ( _IPHONE_LEGACY_WIDTH - _IOS_LEGACY_STATUSBAR_HEIGHT ) ) ); + } else + return 0; + } + + static private function queryIphoneFour():uint { + if ( queryIOS() ){ + stageValidate(); + if ( isLandscape() ) + return ( uint( _STAGE.fullScreenWidth == _IPHONE_RETINA_FOUR_HEIGHT ) || uint( _STAGE.fullScreenWidth == ( _IPHONE_RETINA_FOUR_HEIGHT - _IOS_RETINA_STATUSBAR_HEIGHT ) ) ); + else + return ( uint( _STAGE.fullScreenWidth == _IPHONE_RETINA_FOUR_WIDTH ) || uint( _STAGE.fullScreenWidth == ( _IPHONE_RETINA_FOUR_WIDTH - _IOS_RETINA_STATUSBAR_HEIGHT ) ) ); + } else + return 0; + } + + static private function queryIphoneFive():uint { + if ( queryIOS() ){ + stageValidate(); + if ( isLandscape() ) + return ( uint( _STAGE.fullScreenWidth == _IPHONE_RETINA_FIVE_HEIGHT ) || uint( _STAGE.fullScreenWidth == ( _IPHONE_RETINA_FIVE_HEIGHT - _IOS_RETINA_STATUSBAR_HEIGHT ) ) ); + else + return ( uint( _STAGE.fullScreenWidth == _IPHONE_RETINA_FIVE_WIDTH ) || uint( _STAGE.fullScreenWidth == ( _IPHONE_RETINA_FIVE_WIDTH - _IOS_RETINA_STATUSBAR_HEIGHT ) ) ); + } else + return 0; + } + + static private function queryIphoneSix():uint { + if ( queryIOS() ){ + stageValidate(); + if ( isLandscape() ) + return ( uint( _STAGE.fullScreenWidth == _IPHONE_RETINA_SIX_HEIGHT ) || uint( _STAGE.fullScreenWidth == ( _IPHONE_RETINA_SIX_HEIGHT - _IOS_RETINA_STATUSBAR_HEIGHT ) ) ); + else + return ( uint( _STAGE.fullScreenWidth == _IPHONE_RETINA_SIX_WIDTH ) || uint( _STAGE.fullScreenWidth == ( _IPHONE_RETINA_SIX_WIDTH - _IOS_RETINA_STATUSBAR_HEIGHT ) ) ); + } else + return 0; + } + + static private function queryIphoneSixPlus():uint { + if ( queryIOS() ){ + stageValidate(); + if ( isLandscape() ) + return ( uint( _STAGE.fullScreenWidth == _IPHONE_RETINA_SIX_PLUS_HEIGHT ) || uint( _STAGE.fullScreenWidth == ( _IPHONE_RETINA_SIX_PLUS_HEIGHT - _IOS_RETINA_STATUSBAR_HEIGHT ) ) ); + else + return ( uint( _STAGE.fullScreenWidth == _IPHONE_RETINA_SIX_PLUS_WIDTH ) || uint( _STAGE.fullScreenWidth == ( _IPHONE_RETINA_SIX_PLUS_WIDTH - _IOS_RETINA_STATUSBAR_HEIGHT ) ) ); + } else + return 0; + } + + static private function queryIpadLegacy():uint { + if ( queryIOS() ){ + if ( isLandscape() ) + return ( uint( _STAGE.fullScreenWidth == _IPAD_LEGACY_HEIGHT ) || uint( _STAGE.fullScreenWidth == ( _IPAD_LEGACY_HEIGHT - _IOS_LEGACY_STATUSBAR_HEIGHT ) ) ); + else + return ( uint( _STAGE.fullScreenWidth == _IPAD_LEGACY_WIDTH ) || uint( _STAGE.fullScreenWidth == ( _IPAD_LEGACY_WIDTH - _IOS_LEGACY_STATUSBAR_HEIGHT ) ) ); + } else + return 0; + } + + static private function queryIpadRetina():uint { + if ( queryIOS() ){ + if ( isLandscape() ) + return ( uint( _STAGE.fullScreenWidth == _IPAD_RETINA_HEIGHT ) || uint( _STAGE.fullScreenWidth == ( _IPAD_RETINA_HEIGHT - _IOS_RETINA_STATUSBAR_HEIGHT ) ) ); + else + return ( uint( _STAGE.fullScreenWidth == _IPAD_RETINA_WIDTH ) || uint( _STAGE.fullScreenWidth == ( _IPAD_RETINA_WIDTH - _IOS_RETINA_STATUSBAR_HEIGHT ) ) ); + } else + return 0; + } + + ///maybe can be updated further + static private function queryIOS():uint { + _PLATFORM_IS_IOS = uint( _PLAYER_VERSION == "IOS" ); + return _PLATFORM_IS_IOS; + } + + static private function queryAndroid():uint { + _PLATFORM_IS_AND = uint( _PLAYER_VERSION == "AND" ); + if ( 1 == _PLATFORM_IS_AND ){ + //*...query everything Android + + //*/ + } + return _PLATFORM_IS_AND; + } + + static private function queryBrowser():uint { + //it is indeed possible to get a more specific browser.. ( but I don't think that it is a very accurate result ) + _PLATFORM_IS_BROWSER = ( uint( _PLAYER_TYPE == "PlugIn" ) || uint( _PLAYER_TYPE == "ActiveX" ) ); + return _PLATFORM_IS_BROWSER; + } + } +} \ No newline at end of file diff --git a/src/citrus/utils/SoundChannelUtil.as b/src/citrus/utils/SoundChannelUtil.as new file mode 100644 index 00000000..1dac8ea3 --- /dev/null +++ b/src/citrus/utils/SoundChannelUtil.as @@ -0,0 +1,114 @@ +package citrus.utils +{ + import flash.events.Event; + import flash.media.Sound; + import flash.media.SoundChannel; + import flash.media.SoundTransform; + import flash.utils.ByteArray; + + public class SoundChannelUtil + { + private static var _soundCheck:Sound; + private static var soundChannel:SoundChannel; + + private static var _silentSound:Sound; + private static var silentChannel:SoundChannel; + + private static var _silentSoundTransform:SoundTransform = new SoundTransform(0, 0); + + public static function hasAvailableChannel():Boolean + { + soundChannel = soundCheck.play(0, 0, silentST); + + if (soundChannel != null) + { + soundChannel.stop(); + soundChannel = null; + return true; + } + else + return false; + } + + public static function maxAvailableChannels():uint + { + var channels:Vector. = new Vector.(); + var len:uint = 0; + + while ((soundChannel = soundCheck.play(0, 0, silentST)) != null) + channels.push(soundChannel); + + len = channels.length; + + while ((soundChannel = channels.pop()) != null) + soundChannel.stop(); + + channels.length = 0; + + return len; + + } + + public static function get silentST():SoundTransform + { + return _silentSoundTransform; + } + + public static function get soundCheck():Sound + { + if (!_soundCheck) + _soundCheck = generateSound(); + return _soundCheck; + } + + public static function get silentSound():Sound + { + if (!_silentSound) + _silentSound = generateSound(2048,0); + return _silentSound; + } + + public static function playSilentSound():Boolean + { + if (silentChannel) + return false; + silentChannel = silentSound.play(0, int.MAX_VALUE, silentST); + if (silentChannel) + { + silentChannel.addEventListener(Event.SOUND_COMPLETE, silentComplete); + return true; + } + else + return false; + } + + public static function stopSilentSound():void + { + if (silentChannel) + { + silentChannel.stop(); + silentChannel.removeEventListener(Event.SOUND_COMPLETE, silentComplete); + silentChannel = null; + } + } + + private static function generateSound(length:int = 1,val:Number = 1.0):Sound + { + var sound:Sound = new Sound(); + var soundBA:ByteArray = new ByteArray(); + var i:int = 0; + for (; i < length; i++) + soundBA.writeFloat(val); + soundBA.position = 0; + sound.loadPCMFromByteArray(soundBA, 1, "float", false, 44100); + return sound; + } + + private static function silentComplete(e:Event):void + { + silentChannel = silentSound.play(0, int.MAX_VALUE, silentST); + } + + } + +} \ No newline at end of file diff --git a/src/citrus/utils/objectmakers/ObjectMaker2D.as b/src/citrus/utils/objectmakers/ObjectMaker2D.as new file mode 100644 index 00000000..e08d324c --- /dev/null +++ b/src/citrus/utils/objectmakers/ObjectMaker2D.as @@ -0,0 +1,460 @@ +package citrus.utils.objectmakers { + + import citrus.core.CitrusEngine; + import citrus.core.CitrusObject; + import citrus.core.IState; + import citrus.objects.CitrusSprite; + import citrus.utils.objectmakers.tmx.TmxLayer; + import citrus.utils.objectmakers.tmx.TmxMap; + import citrus.utils.objectmakers.tmx.TmxObject; + import citrus.utils.objectmakers.tmx.TmxObjectGroup; + import citrus.utils.objectmakers.tmx.TmxTileSet; + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.MovieClip; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + import flash.utils.getDefinitionByName; + + /** + * The ObjectMaker is a factory utility class for quickly and easily batch-creating a bunch of CitrusObjects. + * Usually the ObjectMaker is used if you laid out your level in a level editor or an XML file. + * Pass in your layout object (SWF, XML, or whatever else is supported in the future) to the appropriate method, + * and the method will return an array of created CitrusObjects. + * + *

    The methods within the ObjectMaker should be called according to what kind of layout file that was created + * by your level editor.

    + */ + public class ObjectMaker2D { + + public function ObjectMaker2D() { + } + + /** + * You can pass a custom-created MovieClip object into this method to auto-create CitrusObjects. + * This method looks at all the children of the MovieClip you passed in, and creates a CitrusObject with the + * x, y, width, height, name, and rotation of the of MovieClip. + * + *

    You may use the powerful Inspectable metadata tag : in your fla file, add the path to the libraries and + * the swcs. Then create your MovieClip, right click on it and convert as a component. Inform the package and class. + * You will have access to all its properties.

    + * + *

    You can also add properties directly in your MovieClips, follow this step :

    + * + *

    In order for this to properly create a CitrusObject from a MovieClip, the MovieClip needs to have a variable + * called classPath on it, which will provide, in String form, the full + * package and class name of the Citrus Object that it is supposed to create (such as "myGame.MyHero"). You can specify + * this in frame 1 of the MovieClip asset in Flash.

    + * + *

    You can also initialize your CitrusObject's parameters by creating a "params" variable (of type Object) + * on your MovieClip. The "params" object will be passed into the newly created CitrusObject.

    + * + *

    So, within the first frame of each child-MovieClip of the "layout" MovieClip, + * you should specify something like the following:

    + * + *

    var classPath="citrus.objects.platformer.Hero";

    + * + *

    var params={view: "Patch.swf", jumpHeight: 14};

    + */ + public static function FromMovieClip(mc:MovieClip, addToCurrentState:Boolean = true , forceFrame:uint = 1):Array { + + //force mc to given frame to avoid undefined properties defined in action frames. + mc.gotoAndStop(forceFrame); + + var a:Array = []; + var n:Number = mc.numChildren; + var child:MovieClip; + for (var i:uint = 0; i < n; ++i) { + child = mc.getChildAt(i) as MovieClip; + if (child) { + if (!child.className) + continue; + + var objectClass:Class = getDefinitionByName(child.className) as Class; + var params:Object = {}; + + if (child.params) + params = child.params; + + params.x = child.x; + params.y = child.y; + + // We need to unrotate the object to get its true width/height. Then rotate it back. + var rotation:Number = child.rotation; + child.rotation = 0; + params.width = child.width; + params.height = child.height; + child.rotation = rotation; + + params.rotation = child.rotation; + + // Adding properties from the component inspector + for (var metatags:String in child) { + if (metatags != "componentInspectorSetting" && metatags != "className") { + params[metatags] = child[metatags]; + } + } + + var object:CitrusObject = new objectClass(child.name, params); + a.push(object); + } + } + + if (addToCurrentState) { + var ce:CitrusEngine = CitrusEngine.getInstance(); + for each (object in a) + ce.state.add(object); + } + + return a; + } + + /** + * The Citrus Engine supports the Tiled Map Editor. + *

    It supports different layers, objects creation and Tilesets.

    + * + *

    You can add properties inside layers (group, parallax...), they are processed as Citrus Sprite.

    + *

    Polygons are supported but must be drawn clockwise in TiledMap editor to work correctly.

    + * + *

    For the objects, you can add their name and don't forget their types : package name + class name. + * It also supports properties.

    + * @param levelXML the TMX provided by the Tiled Map Editor software, convert it into an xml before. + * @param images an array of bitmap used by tileSets. The name of the bitmap must correspond to the tileSet image source name. + * @param addToCurrentState Automatically adds all CitrusObjects that get created to the current state. + * @return An array of CitrusObject with all objects created. + * @see CitrusObject + */ + public static function FromTiledMap(levelXML:XML, images:Array, addToCurrentState:Boolean = true):Array { + var objects:Array = []; + var map:TmxMap = new TmxMap(levelXML); + + for each(var layer:Object in map.layers_ordered) { + if (layer is TmxLayer) { + addTiledLayer(map, layer as TmxLayer, images, objects); + }else if (layer is TmxObjectGroup) { + addTiledObjectgroup(layer as TmxObjectGroup, objects); + }else { + throw new Error('Found layer type not supported.'); + } + } + + const ce:CitrusEngine = CitrusEngine.getInstance(); + if (addToCurrentState) { + for each (var object:CitrusObject in objects) { + ce.state.add(object); + } + } + + return objects; + } + + static private function addTiledLayer(map:TmxMap, layer:TmxLayer, images:Array, objects:Array):void { + // Bits on the far end of the 32-bit global tile ID are used for tile flags + const FLIPPED_DIAGONALLY_FLAG:uint = 0x20000000; + const FLIPPED_VERTICALLY_FLAG:uint = 0x40000000; + const FLIPPED_HORIZONTALLY_FLAG:uint = 0x80000000; + const FLIPPED_FLAGS_MASK:uint = ~(FLIPPED_HORIZONTALLY_FLAG | FLIPPED_VERTICALLY_FLAG | FLIPPED_DIAGONALLY_FLAG); + const _90degInRad:Number = Math.PI * 0.5; + + var params:Object; + + var bmp:Bitmap; + var useBmpSmoothing:Boolean; + + const tileRect:Rectangle = new Rectangle; + tileRect.width = map.tileWidth; + tileRect.height = map.tileHeight; + + const mapTiles:Array = layer.tileGIDs; + const rows:uint = mapTiles.length; + var columns:uint; + + const flipMatrix:Matrix = new Matrix; + const flipBmp:BitmapData = new BitmapData(map.tileWidth, map.tileHeight, true, 0); + const flipBmpRect:Rectangle = new Rectangle(0, 0, map.tileWidth, map.tileHeight); + + const tileDestInLayer:Point = new Point; + var pathSplit:Array; + var tilesetImageName:String; + + const layerBmp:BitmapData = new BitmapData(map.width * map.tileWidth, map.height * map.tileHeight, true, 0); + + for each (var tileSet:TmxTileSet in map.tileSets) { + + pathSplit = tileSet.imageSource.split("/"); + tilesetImageName = pathSplit[pathSplit.length - 1]; + + for each (var image:Bitmap in images) { + + var flag:Boolean = false; + + if (tilesetImageName == image.name) { + flag = true; + bmp = image; + break; + } + } + + if (!flag || bmp == null) { + throw new Error("ObjectMaker didn't find an image name corresponding to the tileset imagesource name: " + tileSet.imageSource + ", add its name to your bitmap."); + } + + useBmpSmoothing ||= bmp.smoothing; + + tileSet.image = bmp.bitmapData; + + for (var layerRow:uint = 0; layerRow < rows; ++layerRow) { + + columns = mapTiles[layerRow].length; + + for (var layerColumn:uint = 0; layerColumn < columns; ++layerColumn) { + + var tileGID:uint = mapTiles[layerRow][layerColumn]; + + // Read out the flags + var flipped_horizontally:Boolean = (tileGID & FLIPPED_HORIZONTALLY_FLAG) != 0; + var flipped_vertically:Boolean = (tileGID & FLIPPED_VERTICALLY_FLAG) != 0; + var flipped_diagonally:Boolean = (tileGID & FLIPPED_DIAGONALLY_FLAG) != 0; + + // Clear the flags + tileGID &= FLIPPED_FLAGS_MASK; + + if (tileGID != 0) { + + var tilemapRow:int = (tileGID - 1) / tileSet.numCols; + var tilemapCol:int = (tileGID - 1) % tileSet.numCols; + + tileRect.x = tilemapCol * map.tileWidth; + tileRect.y = tilemapRow * map.tileHeight; + + tileDestInLayer.x = layerColumn * map.tileWidth; + tileDestInLayer.y = layerRow * map.tileHeight; + + // Handle flipped tiles + if (flipped_diagonally || flipped_horizontally || flipped_vertically) { + + // We will flip the tilemap image using the center of the current tile + var tileCenterX:Number = tileRect.x + tileRect.width * 0.5; + var tileCenterY:Number = tileRect.y + tileRect.height * 0.5; + + flipMatrix.identity(); + flipMatrix.translate(-tileCenterX, -tileCenterY); + + if (flipped_diagonally) { + if (flipped_horizontally) { + flipMatrix.rotate(_90degInRad); + if (flipped_vertically) { + flipMatrix.scale(1, -1); + } + } else { + flipMatrix.rotate(-_90degInRad); + if (!flipped_vertically) { + flipMatrix.scale(1, -1); + } + } + } else { + if (flipped_horizontally) { + flipMatrix.scale(-1, 1); + } + + if (flipped_vertically) { + flipMatrix.scale(1, -1); + } + } + + flipMatrix.translate(tileCenterX, tileCenterY); + flipMatrix.translate(-tileRect.x, -tileRect.y); + + // clear the buffer and draw + flipBmp.fillRect(flipBmpRect, 0); + flipBmp.draw(bmp.bitmapData, flipMatrix, null, null, flipBmpRect); + + layerBmp.copyPixels(flipBmp, flipBmpRect, tileDestInLayer); + } else { + layerBmp.copyPixels(bmp.bitmapData, tileRect, tileDestInLayer); + } + } + } + } + } + + var bmpFinal:Bitmap = new Bitmap(layerBmp); + bmpFinal.smoothing = useBmpSmoothing; + + params = {}; + params.view = bmpFinal; + + flipBmp.dispose(); + + for (var param:String in layer.properties) { + params[param] = layer.properties[param]; + } + + objects.push(new CitrusSprite(layer.name, params)); + } + + static private function addTiledObjectgroup(group:TmxObjectGroup, objects:Array):void { + var objectClass:Class; + var object:CitrusObject; + var params:Object; + + for each (var objectTmx:TmxObject in group.objects) { + + objectClass = getDefinitionByName(objectTmx.type) as Class; + + params = {}; + + for (var param:String in objectTmx.custom) { + params[param] = objectTmx.custom[param]; + } + + params.x = objectTmx.x + objectTmx.width * 0.5; + params.y = objectTmx.y + objectTmx.height * 0.5; + params.width = objectTmx.width; + params.height = objectTmx.height; + params.rotation = objectTmx.rotation; + + // Polygon/Polyline support + if (objectTmx.points != null) { + params.points = objectTmx.points; + } + + object = new objectClass(objectTmx.name, params); + objects.push(object); + } + } + + /** + * This batch-creates CitrusObjects from an XML file generated by the level editor GLEED2D. If you would like to + * use GLEED2D as a level editor for your Citrus Engine game, call this function to parse your GLEED2D level. + * + *

    When using GLEED2D, there are a few things to note: + *

      + *
    • You must add a custom property named 'className' for each object you make, unless it will be of the type + * specified in the defaultClassName parameter. Assign this property a value + * that represents the class that you want that object to be. For example, if you wanted to make a hero, you must + * give your GLEED2D Hero 'className' property the value 'citrus.objects.platformer.Hero'. Don't forget + * to include the package, or the Citrus Engine won't be able to make your object.
    • + *
    • You can shift-click and drag to duplicate GLEED2D objects. This is the easiest way to copy an entire object, + * custom-properties and all.
    • + *
    • Unfortunately, GLEED2D does not support rotating the Rectangle Primitive, this makes GLEED2D difficult to use + * if you plan on using it to layout levels for a platformer with hills. You can, however, specify a custom property + * named "rotation", which will work in Citrus Engine, but not be reflected in GLEED2D.
    • + *
    • GLEED2D does not support SWFs as textures, so any CitrusObjects that will use SWFs as their view should + * be created via a GLEED2D rectangle primitive, then specify the SWF path or MovieClip class name using a custom + * property named 'view'. + *
    • + *
    + *

    + * + * @param levelXML An XML level object created by GLEED2D. + * @param addToCurrentState Automatically adds all CitrusObjects that get created to the current state. + * @param layerIndexProperty Gleed's layer indexes will be assigned to the specified property. + * @param defaultClassName If a className custom property is not specified on a GLEED2D asset, this is the default CitrusObject class that gets created. + * @return An array of CitrusObjects. If the addToCurrentState property is false, you will still need to add these to the state. + * + */ + public static function FromGleed(levelXML:XML, addToCurrentState:Boolean = true, layerIndexProperty:String = "group", defaultClassName:String = "citrus.objects.CitrusSprite"):Array { + var layerIndex:uint = 0; + var items:Array = []; + var object:Object; + var objectName:String; + var ce:CitrusEngine = CitrusEngine.getInstance(); + for each (var layerXML:XML in levelXML.Layers.Layer) // Loop through all layers + { + for each (var itemXML:XML in layerXML.Items.Item) // Loop through all items on a layer + { + // Grab the XML properties we want off of the item node. + objectName = itemXML.@Name.toString(); + var x:Number = itemXML.Position.X.toString(); + // Top for primitives, center for textures + var y:Number = itemXML.Position.Y.toString(); + // Left for primitives, center for textures + + // See if this object has a texture + var viewString:String = itemXML.texture_filename.toString(); + if (viewString != "") { + // Create known params for a GLEED2D "texture" + var originX:Number = itemXML.Origin.X.toString(); + var originY:Number = itemXML.Origin.Y.toString(); + var rotation:Number = Number(itemXML.Rotation.toString()) * 180 / Math.PI; + object = {x: x, y: y, width: originX * 2, height: originY * 2, rotation: rotation, registration: "center"}; + viewString = Replace(viewString, "\\", "/"); + // covert backslashes to forward slashes + object.view = viewString; + } else { + // Create known params for a GLEED2D "primitive" + var width:Number = itemXML.Width.toString(); + var height:Number = itemXML.Height.toString(); + + object = {x: x + (width / 2), y: y + (height / 2), width: width, height: height}; + } + + // Covert GLEED layer index to a property on the object. + if (layerIndexProperty) + object[layerIndexProperty] = layerIndex; + + // Add the custom properties + var className:String = defaultClassName; + for each (var customPropXML:XML in itemXML.CustomProperties.Property) { + if (customPropXML.@Name.toString() == "className") + className = customPropXML.string.toString(); + else + object[customPropXML.@Name.toString()] = customPropXML.string.toString(); + } + + // Make the CitrusObject and add it to the current state. + var citrusClass:Class = getDefinitionByName(className) as Class; + var citrusObject:CitrusObject = new citrusClass(objectName, object); + if (addToCurrentState) + ce.state.add(citrusObject); + + items.push(citrusObject); + } + layerIndex++; + } + return items; + } + + /** + * This function batch-creates Citrus Engine game objects from an XML file generated by the Level Architect. + * If you are using the Level Architect as your level editor, call this function to parse the objects in + * your Level Architect level. + * + * @param levelData The XML file (.lev) that the Level Architect generates. + * @param addToCurrentState If true, the objects that are created will get added to the current state's object list. + * @return Returns an array containing all the objects that were created via this function. + */ + public static function FromLevelArchitect(levelData:XML, addToCurrentState:Boolean = true):Array { + var array:Array = []; + + var state:IState = CitrusEngine.getInstance().state; + for each (var objectXML:XML in levelData.CitrusObject) { + var params:Object = {}; + for each (var paramXML:XML in objectXML.Property) { + params[paramXML.@name] = paramXML.toString(); + }var className:String = objectXML.@className; + try { + var theClass:Class = getDefinitionByName(className) as Class; + } catch (e:Error) { + if (e.errorID == 1065) { + throw new Error("You (yes, YOU) must import and create a reference to the " + className + " class somewhere in your code. The Level Architect cannot create objects unless they are compiled into the SWF."); + } else { + throw e; + } + } + var theObject:CitrusObject = new theClass(objectXML.@name, params); + array.push(theObject); + if (addToCurrentState) + state.add(theObject); + } + + return array; + } + + private static function Replace(str:String, fnd:String, rpl:String):String { + return str.split(fnd).join(rpl); + } + } +} \ No newline at end of file diff --git a/src/citrus/utils/objectmakers/ObjectMaker3D.as b/src/citrus/utils/objectmakers/ObjectMaker3D.as new file mode 100644 index 00000000..530cdb47 --- /dev/null +++ b/src/citrus/utils/objectmakers/ObjectMaker3D.as @@ -0,0 +1,109 @@ +package citrus.utils.objectmakers { + + import away3d.entities.Mesh; + import away3d.materials.ColorMaterial; + import away3d.primitives.CubeGeometry; + import away3d.primitives.SphereGeometry; + + import citrus.core.CitrusEngine; + import citrus.objects.AwayPhysicsObject; + import citrus.objects.platformer.awayphysics.Platform; + + /** + * The ObjectMaker is a factory utility class for quickly and easily batch-creating a bunch of CitrusObjects. + * Usually the ObjectMaker is used if you laid out your level in a level editor or an XML file. + * Pass in your layout object (SWF, XML, or whatever else is supported in the future) to the appropriate method, + * and the method will return an array of created CitrusObjects. + * + *

    The methods within the ObjectMaker3D should be called according to what kind of layout file that was created + * by your level editor.

    + */ + public class ObjectMaker3D { + + public function ObjectMaker3D() { + } + + /** + * The Citrus Engine supports the Cadet Editor 3D. + *

    It supports physics objects creation (Plane, Cube, Sphere).

    + */ + public static function FromCadetEditor3D(levelData:XML, addToCurrentState:Boolean = true):Array { + + var ce:CitrusEngine = CitrusEngine.getInstance(); + + var params:Object; + + var objects:Array = []; + + var type:String; + var radius:Number; + + var object:AwayPhysicsObject; + + for each (var root:XML in levelData.children()) { + for each (var parent:XML in root.children()) { + + type = parent.@name; + + if (type == "Cube" || type == "Plane" || type == "Sphere") { + + var transform:Array = parent.@transform.split(","); + + params = {}; + params.x = transform[12]; + params.y = transform[13]; + params.z = transform[14]; + + for each (var child:XML in parent.children()) { + + for each (var finalElement:XML in child.children()) { + + params.width = finalElement.@width; + params.height = finalElement.@height; + params.depth = finalElement.@depth; + radius = finalElement.@radius; + + if (radius) + params.radius = finalElement.@radius; + + if (type == "Plane") { + + // the plane seems to use the height as the depth + params.depth = params.height; + params.height = 0; + params.view = new Mesh(new CubeGeometry(params.width, params.height, params.depth), new ColorMaterial(0xFF0000)); + object = new Platform("plane", params); + + } else { + + if (params.radius) { + + params.view = new Mesh(new SphereGeometry(params.radius), new ColorMaterial(0x00FF00)); + object = new AwayPhysicsObject("sphere", params); + + } else { + + params.view = new Mesh(new CubeGeometry(params.width, params.height, params.depth), new ColorMaterial(0x0000FF)); + object = new AwayPhysicsObject("cube", params); + } + } + + objects.push(object); + } + } + + } + } + } + + if (addToCurrentState) + for each (object in objects) ce.state.add(object); + + return objects; + } + + private static function Replace(str:String, fnd:String, rpl:String):String { + return str.split(fnd).join(rpl); + } + } +} \ No newline at end of file diff --git a/src/citrus/utils/objectmakers/ObjectMakerStarling.as b/src/citrus/utils/objectmakers/ObjectMakerStarling.as new file mode 100644 index 00000000..16d32c24 --- /dev/null +++ b/src/citrus/utils/objectmakers/ObjectMakerStarling.as @@ -0,0 +1,414 @@ +package citrus.utils.objectmakers { + + import citrus.utils.objectmakers.tmx.TmxLayer; + import flash.geom.Point; + import flash.geom.Matrix; + import citrus.core.CitrusEngine; + import citrus.core.CitrusObject; + import citrus.objects.CitrusSprite; + import citrus.utils.objectmakers.tmx.TmxMap; + import citrus.utils.objectmakers.tmx.TmxObject; + import citrus.utils.objectmakers.tmx.TmxObjectGroup; + import citrus.utils.objectmakers.tmx.TmxPropertySet; + import citrus.utils.objectmakers.tmx.TmxTileSet; + + import starling.display.Image; + import starling.display.QuadBatch; + import starling.textures.Texture; + import starling.textures.TextureAtlas; + import starling.utils.Color; + + import flash.display.MovieClip; + import flash.utils.getDefinitionByName; + + /** + * The ObjectMaker is a factory utility class for quickly and easily batch-creating a bunch of CitrusObjects. + * Usually the ObjectMaker is used if you laid out your level in a level editor or an XML file. + * Pass in your layout object (SWF, XML, or whatever else is supported in the future) to the appropriate method, + * and the method will return an array of created CitrusObjects. + * + *

    The methods within the ObjectMaker should be called according to what kind of layout file that was created + * by your level editor.

    + */ + public class ObjectMakerStarling { + + public function ObjectMakerStarling() { + } + + /** + * You can pass a custom-created MovieClip object into this method to auto-create CitrusObjects. + * This method looks at all the children of the MovieClip you passed in, and creates a CitrusObject with the + * x, y, width, height, name, and rotation of the of MovieClip. + * + *

    You may use the powerful Inspectable metadata tag : in your fla file, add the path to the libraries and + * the swcs. Then create your MovieClip, right click on it and convert as a component. Inform the package and class. + * You will have access to all its properties.

    + * + *

    You can also add properties directly in your MovieClips, follow this step :

    + * + *

    In order for this to properly create a CitrusObject from a MovieClip, the MovieClip needs to have a variable + * called classPath on it, which will provide, in String form, the full + * package and class name of the Citrus Object that it is supposed to create (such as "myGame.MyHero"). You can specify + * this in frame 1 of the MovieClip asset in Flash.

    + * + *

    You can also initialize your CitrusObject's parameters by creating a "params" variable (of type Object) + * on your MovieClip. The "params" object will be passed into the newly created CitrusObject.

    + * + *

    So, within the first frame of each child-MovieClip of the "layout" MovieClip, + * you should specify something like the following:

    + * + *

    var classPath="citrus.objects.platformer.Hero";

    + * + *

    var params={view: "Patch.swf", jumpHeight: 14};

    + * + *

    This Starling version enables you to use a String for the view which is a texture name coming from your texture atlas.

    + * + * @param textureAtlas A TextureAtlas or an AssetManager object containing textures which are used in your level maker. + */ + public static function FromMovieClip(mc:MovieClip, textureAtlas:*, addToCurrentState:Boolean = true, forceFrame:uint = 1):Array { + + //force mc to given frame to avoid undefined properties defined in action frames. + mc.gotoAndStop(forceFrame); + + var a:Array = []; + var n:Number = mc.numChildren; + var child:MovieClip; + for (var i:uint = 0; i < n; ++i) { + child = mc.getChildAt(i) as MovieClip; + if (child) { + if (!child.className) + continue; + + var objectClass:Class = getDefinitionByName(child.className) as Class; + var params:Object = {}; + + if (child.params) + params = child.params; + + params.x = child.x; + params.y = child.y; + + // We need to unrotate the object to get its true width/height. Then rotate it back. + var rotation:Number = child.rotation; + child.rotation = 0; + params.width = child.width; + params.height = child.height; + child.rotation = rotation; + + params.rotation = child.rotation; + + // Adding properties from the component inspector + for (var metatags:String in child) { + if (metatags != "componentInspectorSetting" && metatags != "className") { + params[metatags] = child[metatags]; + } + } + + if (params.view && !(params.view is Image)) { + + var suffix:String = params.view.substring(params.view.length - 4).toLowerCase(); + if (!(suffix == ".swf" || suffix == ".png" || suffix == ".gif" || suffix == ".jpg")) { + if (textureAtlas) + params.view = new Image(textureAtlas.getTexture(params.view)); + else + throw new Error("ObjectMakerStarling FromMovieClip function needs a TextureAtlas or a reference to an AssetManager!"); + } + } + + var object:CitrusObject = new objectClass(child.name, params); + a.push(object); + } + } + + if (addToCurrentState) { + var ce:CitrusEngine = CitrusEngine.getInstance(); + for each (object in a) + ce.state.add(object); + } + + return a; + } + + /** + * The Citrus Engine supports the Tiled Map Editor. + *

    It supports different layers, objects creation and a Tilesets.

    + * + *

    You can add properties inside layers (group, parallax...), they are processed as Citrus Sprite.

    + *

    Polygons are supported but must be drawn clockwise in TiledMap editor to work correctly.

    + * + *

    For the objects, you can add their name and don't forget their types : package name + class name. + * It also supports properties.

    + * @param levelXML the TMX provided by the Tiled Map Editor software, convert it into an xml before. + * @param atlas an atlas or a reference to an AssetManager which represent the different tiles, you must name each tile with the corresponding texture name. + */ + public static function FromTiledMap(levelXML:XML, atlas:*, addToCurrentState:Boolean = true):Array { + var objects:Array = []; + var tmx:TmxMap = new TmxMap(levelXML); + + for each (var layer:Object in tmx.layers_ordered) { + if (layer is TmxLayer) { + addTiledLayer(tmx, atlas, layer as TmxLayer, objects); + } else if (layer is TmxObjectGroup) { + addTiledObjectgroup(tmx, atlas, layer as TmxObjectGroup, objects); + } + } + + const ce:CitrusEngine = CitrusEngine.getInstance(); + if (addToCurrentState) + for each (var object:CitrusObject in objects) + ce.state.add(object); + + return objects; + } + + static private function addTiledLayer(tmx:TmxMap, atlas:*, layer:TmxLayer, objects:Array):void { + var mapTiles:Array = layer.tileGIDs; + var mapTilesX:uint = mapTiles.length; + var mapTilesY:uint; + + var tileSet:TmxTileSet; + var tileProps:TmxPropertySet; + var name:String; + var texture:Texture; + + var qb:QuadBatch = new QuadBatch(); + + for (var i:uint = 0; i < mapTilesX; ++i) { + + mapTilesY = mapTiles[i].length; + + for (var j:uint = 0; j < mapTilesY; ++j) { + + if (mapTiles[i][j] != 0) { + + var tileID:uint = mapTiles[i][j]; + + for each (tileSet in tmx.tileSets) { + tileProps = tileSet.getProperties(tileID - tileSet.firstGID); + if (tileProps != null) + break; + } + name = tileProps["name"]; + + texture = atlas.getTexture(name); + + var image:Image = new Image(texture); + image.x = j * tmx.tileWidth; + image.y = i * tmx.tileHeight; + + qb.addImage(image); + } + } + } + + var params:Object = {}; + params.view = qb; + + for (var param:String in layer.properties) { + params[param] = layer.properties[param]; + } + + objects.push(new CitrusSprite(layer.name, params)); + } + + static private function addTiledObjectgroup(tmx:TmxMap, atlas:*, group:TmxObjectGroup, objects:Array):void { + var objectClass:Class; + var object:CitrusObject; + + var tileSet:TmxTileSet; + var tileProps:TmxPropertySet; + var name:String; + + var mtx:Matrix = new Matrix(); + var pt:Point = new Point(); + var newLoc:Point; + var objectTmx:TmxObject; + + for each (objectTmx in group.objects) { + + objectClass = getDefinitionByName(objectTmx.type) as Class; + var params:Object = {}; + + for (var param:String in objectTmx.custom) { + params[param] = objectTmx.custom[param]; + } + + params.x = objectTmx.x + objectTmx.width * 0.5; + params.y = objectTmx.y + objectTmx.height * 0.5; + params.width = objectTmx.width; + params.height = objectTmx.height; + params.rotation = objectTmx.rotation; + + if (params.rotation != 0) { + mtx.identity(); + mtx.rotate(objectTmx.rotation * Math.PI / 180); + pt.setTo(objectTmx.width / 2, objectTmx.height / 2); + newLoc = mtx.transformPoint(pt); + params.x = objectTmx.x + newLoc.x; + params.y = objectTmx.y + newLoc.y; + } + + if (objectTmx.custom && objectTmx.custom["view"]) { + params.view = atlas.getTexture(objectTmx.custom["view"]); + + } else if (objectTmx.gid != 0) { // for handling image objects in Tiled + for each (tileSet in tmx.tileSets) { + tileProps = tileSet.getProperties(objectTmx.gid - tileSet.firstGID); + if (tileProps != null) + break; + } + name = tileProps["name"]; + params.view = atlas.getTexture(name); + params.width = Texture(params.view).frame.width; + params.height = Texture(params.view).frame.height; + params.x += params.width / 2; + params.y -= params.height / 2; + } + + // Polygon/Polyline support + if (objectTmx.shapeType != null) { + params.shapeType = objectTmx.shapeType; + params.points = objectTmx.points; + } + + object = new objectClass(objectTmx.name, params); + objects.push(object); + } + } + + /** + * This batch-creates CitrusObjects from an XML file generated by the level editor GLEED2D. If you would like to + * use GLEED2D as a level editor for your Citrus Engine game, call this function to parse your GLEED2D level. + * + *

    When using GLEED2D, there are a few things to note: + *

      + *
    • You must add a custom property named 'className' for each object you make, unless it will be of the type + * specified in the defaultClassName parameter. Assign this property a value + * that represents the class that you want that object to be. For example, if you wanted to make a hero, you must + * give your GLEED2D Hero 'className' property the value 'citrus.objects.platformer.Hero'. Don't forget + * to include the package, or the Citrus Engine won't be able to make your object.
    • + *
    • You can shift-click and drag to duplicate GLEED2D objects. This is the easiest way to copy an entire object, + * custom-properties and all.
    • + *
    • Unfortunately, GLEED2D does not support rotating the Rectangle Primitive, this makes GLEED2D difficult to use + * if you plan on using it to layout levels for a platformer with hills. You can, however, specify a custom property + * named "rotation", which will work in Citrus Engine, but not be reflected in GLEED2D.
    • + *
    • GLEED2D does not support SWFs as textures, so any CitrusObjects that will use SWFs as their view should + * be created via a GLEED2D rectangle primitive, then specify the SWF path or MovieClip class name using a custom + * property named 'view'. + *
    • + *
    + *

    + * + * @param levelXML An XML level object created by GLEED2D. + * @param textureAtlas An TextureAtlas that provides all texture within the level. (Note this function supports only single atlas) + * @param addToCurrentState Automatically adds all CitrusObjects that get created to the current state. + * @param layerIndexProperty Gleed's layer indexes will be assigned to the specified property. + * @param defaultClassName If a className custom property is not specified on a GLEED2D asset, this is the default CitrusObject class that gets created. + * @return An array of CitrusObjects. If the addToCurrentState property is false, you will still need to add these to the state. + * + */ + public static function FromGleed(levelXML:XML, textureAtlas:TextureAtlas, addToCurrentState:Boolean = true, layerIndexProperty:String = "group", defaultClassName:String = "citrus.objects.CitrusSprite"):Array { + var objects:Array = []; + var citrusObject:CitrusObject; + var xsiNS:Namespace = new Namespace("xsi", "http://www.w3.org/2001/XMLSchema-instance"); + var ce:CitrusEngine = CitrusEngine.getInstance(); + + for each (var layerXML:XML in levelXML.Layers.Layer) // Loop through all layers + { + var textureItems:Vector. = new Vector.; + var layer:String = layerXML.@Name.toString(); + + for each (var itemXML:XML in layerXML.Items.Item) // Loop through all items on a layer + { + var object:Object = {}; + var objectName:String = itemXML.@Name.toString(); + var x:Number = itemXML.Position.X.toString(); + var y:Number = itemXML.Position.Y.toString(); + var type:String = itemXML.@xsiNS::type.toString(); + var assetString:String = itemXML.asset_name.toString(); + var className:String = defaultClassName; + + // Let's add custom properties + for each (var customPropXML:XML in itemXML.CustomProperties.Property) { + if (customPropXML.@Name.toString() == "className") + { + className = customPropXML.string.toString(); + } + else + { + object[customPropXML.@Name.toString()] = customPropXML.string.toString(); + } + } + + // Let's strip the filename from the texturepath. This is going to be the atlas alias for this texture + assetString = assetString.substr(assetString.lastIndexOf("\\") + 1, assetString.length); + if (assetString) + object.assetString = assetString; + + // If the item is just a TextureItem without any specified class, we will add it to the quadbatch which represents the layer + if (className == defaultClassName && type == "TextureItem" && assetString != "") { + var originX:Number = itemXML.Origin.X.toString(); + var originY:Number = itemXML.Origin.Y.toString(); + var scaleX:Number = itemXML.Scale.X.toString(); + var scaleY:Number = itemXML.Scale.Y.toString(); + var rotation:Number = Number(itemXML.Rotation.toString()); + + // Flip + var flipHorizontally:String = itemXML.FlipHorizontally.toString(); + var flipVertically:String = itemXML.FlipVertically.toString(); + if (flipHorizontally == "true") + scaleX *= -1; + if (flipVertically == "true") + scaleY *= -1; + + // TintColor + var r:int = itemXML.TintColor.R.toString(); + var g:int = itemXML.TintColor.G.toString(); + var b:int = itemXML.TintColor.B.toString(); + var a:int = itemXML.TintColor.A.toString(); + + // Let's create the image that matches the asset string + var image:Image = new Image(textureAtlas.getTexture(assetString)); + image.x = x; + image.y = y; + image.scaleX = scaleX; + image.scaleY = scaleY; + image.pivotX = originX; + image.pivotY = originY; + image.rotation = rotation; + image.color = Color.argb(a, r, g, b); + + // And finally we collect these images for later batching + textureItems.push(image); + } + } + + // We will bundle all TextureItems into single quadbatch + if (textureItems.length > 0) { + var qb:QuadBatch = new QuadBatch(); + + for each (image in textureItems) { + qb.addImage(image); + } + + var citrusSprite:CitrusSprite = new CitrusSprite(layer, { view: qb }); + objects.push(citrusSprite); + } + } + + // Finally we will add everything to the state + if (addToCurrentState) { + for each (citrusObject in objects) { + + if (citrusObject is CitrusSprite) { + citrusSprite = citrusObject as CitrusSprite; + } + + ce.state.add(citrusObject); + } + } + + return objects; + } + } +} diff --git a/src/citrus/utils/objectmakers/tmx/TmxLayer.as b/src/citrus/utils/objectmakers/tmx/TmxLayer.as new file mode 100644 index 00000000..2b5e4350 --- /dev/null +++ b/src/citrus/utils/objectmakers/tmx/TmxLayer.as @@ -0,0 +1,157 @@ +/******************************************************************************* + * Copyright (c) 2010 by Thomas Jahn + * This content is released under the MIT License. + * Questions? Mail me at lithander at gmx.de! + ******************************************************************************/ +package citrus.utils.objectmakers.tmx { + + import flash.utils.ByteArray; + import flash.utils.Endian; + + public class TmxLayer { + + public var map:TmxMap; + public var name:String; + public var x:int; + public var y:int; + public var width:int; + public var height:int; + public var opacity:Number; + public var visible:Boolean; + // tileGIDs[row][column] + public var tileGIDs:Array; + public var properties:TmxPropertySet = null; + + public function TmxLayer(source:XML, parent:TmxMap) { + map = parent; + name = source.@name; + x = source.@x; + y = source.@y; + width = source.@width; + height = source.@height; + visible = !source.@visible || (source.@visible != 0); + opacity = source.@opacity; + + // load properties + var node:XML; + for each (node in source.properties) + properties = properties ? properties.extend(node) : new TmxPropertySet(node); + + // load tile GIDs + tileGIDs = []; + var data:XML = source.data[0]; + if (data) { + var chunk:String = ""; + if (data.@encoding.length() == 0) { + // create a 2dimensional array + var lineWidth:int = width; + var rowIdx:int = -1; + for each (node in data.tile) { + // new line? + if (++lineWidth >= width) { + tileGIDs[++rowIdx] = []; + lineWidth = 0; + } + var gid:int = node.@gid; + tileGIDs[rowIdx].push(gid); + } + } else if (data.@encoding == "csv") { + chunk = data; + tileGIDs = csvToArray(chunk, width); + } else if (data.@encoding == "base64") { + chunk = data; + var compressed:Boolean = false; + if (data.@compression == "zlib") + compressed = true; + else if (data.@compression.length() != 0) + throw Error("TmxLayer - data compression type not supported!"); + + for (var i:int = 0; i < 100; i++) + tileGIDs = base64ToArray(chunk, width, compressed); + } + } + } + + public function toCsv(tileSet:TmxTileSet = null):String { + var max:int = 0xFFFFFF; + var offset:int = 0; + if (tileSet) { + offset = tileSet.firstGID; + max = tileSet.numTiles - 1; + } + var result:String = ""; + for each (var row:Array in tileGIDs) { + var chunk:String = ""; + var id:int = 0; + for each (id in row) { + id -= offset; + if (id < 0 || id > max) + id = 0; + result += chunk; + chunk = id + ","; + } + result += id + "\n"; + } + return result; + } + + public static function csvToArray(input:String, lineWidth:int):Array { + var result:Array = []; + var rows:Array = input.split("\n"); + for each (var row:String in rows) { + var resultRow:Array = []; + var entries:Array = row.split(",", lineWidth); + for each (var entry:String in entries) + resultRow.push(uint(entry)); + // convert to uint + result.push(resultRow); + } + return result; + } + + public static function base64ToArray(chunk:String, lineWidth:int, compressed:Boolean):Array { + var result:Array = []; + var data:ByteArray = base64ToByteArray(chunk); + if (compressed) + data.uncompress(); + data.endian = Endian.LITTLE_ENDIAN; + while (data.position < data.length) { + var resultRow:Array = []; + for (var i:int = 0; i < lineWidth; i++) + resultRow.push(data.readInt()); + result.push(resultRow); + } + return result; + } + + private static const BASE64_CHARS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + + public static function base64ToByteArray(data:String):ByteArray { + var output:ByteArray = new ByteArray(); + // initialize lookup table + var lookup:Array = []; + for (var c:int = 0; c < BASE64_CHARS.length; c++) + lookup[BASE64_CHARS.charCodeAt(c)] = c; + + for (var i:uint = 0; i < data.length - 3; i += 4) { + // read 4 bytes and look them up in the table + var a0:int = lookup[data.charCodeAt(i)]; + var a1:int = lookup[data.charCodeAt(i + 1)]; + var a2:int = lookup[data.charCodeAt(i + 2)]; + var a3:int = lookup[data.charCodeAt(i + 3)]; + + // convert to and write 3 bytes + if (a1 < 64) + output.writeByte((a0 << 2) + ((a1 & 0x30) >> 4)); + if (a2 < 64) + output.writeByte(((a1 & 0x0f) << 4) + ((a2 & 0x3c) >> 2)); + if (a3 < 64) + output.writeByte(((a2 & 0x03) << 6) + a3); + } + + // Rewind & return decoded data + output.position = 0; + return output; + } + } +} \ No newline at end of file diff --git a/src/citrus/utils/objectmakers/tmx/TmxMap.as b/src/citrus/utils/objectmakers/tmx/TmxMap.as new file mode 100644 index 00000000..f3b94f77 --- /dev/null +++ b/src/citrus/utils/objectmakers/tmx/TmxMap.as @@ -0,0 +1,71 @@ +/******************************************************************************* + * Copyright (c) 2010 by Thomas Jahn + * This content is released under the MIT License. + * Questions? Mail me at lithander at gmx.de! + ******************************************************************************/ +/** + * Modified in 2014 by fdufafa: + * Layers from tmx, including object layers, are available as ordered in TiledMapEditor. + */ +package citrus.utils.objectmakers.tmx { + + public class TmxMap { + + public var version:String; + public var orientation:String; + public var width:uint; + public var height:uint; + public var tileWidth:uint; + public var tileHeight:uint; + + public var properties:TmxPropertySet = null; + public var tileSets:Object = {}; + + public var layers_ordered:Array = []; + + static private const TILE_LAYER_NAME:String = 'layer'; + static private const OBJECT_LAYER_NAME:String = 'objectgroup'; + + public function TmxMap(source:XML) { + // map header + version = source.@version ? source.@version : "unknown"; + orientation = source.@orientation ? source.@orientation : "orthogonal"; + width = source.@width; + height = source.@height; + tileWidth = source.@tilewidth; + tileHeight = source.@tileheight; + + // read properties + for each (var node:XML in source.properties) { + properties = properties ? properties.extend(node) : new TmxPropertySet(node); + } + + // load tilesets + for each (node in source.tileset) { + tileSets[node.@name] = new TmxTileSet(node, this); + } + + // load layers of the map in order + for each (node in source.children()) { + if (node.name() == TILE_LAYER_NAME) { + layers_ordered.push(new TmxLayer(node, this)); + }else if (node.name() == OBJECT_LAYER_NAME) { + layers_ordered.push(new TmxObjectGroup(node, this)); + } + } + } + + public function getTileSet(name:String):TmxTileSet { + return tileSets[name] as TmxTileSet; + } + + // works only after TmxTileSet has been initialized with an image... + public function getGidOwner(gid:int):TmxTileSet { + for each (var tileSet:TmxTileSet in tileSets) { + if (tileSet.hasGid(gid)) + return tileSet; + } + return null; + } + } +} \ No newline at end of file diff --git a/src/citrus/utils/objectmakers/tmx/TmxObject.as b/src/citrus/utils/objectmakers/tmx/TmxObject.as new file mode 100644 index 00000000..e928c011 --- /dev/null +++ b/src/citrus/utils/objectmakers/tmx/TmxObject.as @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2010 by Thomas Jahn + * This content is released under the MIT License. + * Questions? Mail me at lithander at gmx.de! + ******************************************************************************/ +package citrus.utils.objectmakers.tmx { + + public class TmxObject { + + public var group:TmxObjectGroup; + public var name:String; + public var type:String; + public var x:int; + public var y:int; + public var width:int; + public var height:int; + public var rotation:int; + public var gid:int; + public var custom:TmxPropertySet; + public var shared:TmxPropertySet; + public var points:Array; + public var shapeType:String; + + public function TmxObject(source:XML, parent:TmxObjectGroup) { + if (!source) + return; + group = parent; + name = source.@name; + type = source.@type; + x = source.@x; + y = source.@y; + width = source.@width; + height = source.@height; + rotation = source.@rotation; + // resolve inheritence + shared = null; + gid = -1; + if (source.@gid.length != 0) // object with tile association? + { + gid = source.@gid; + for each (var tileSet:TmxTileSet in group.map.tileSets) { + shared = tileSet.getPropertiesByGid(gid); + if (shared) + break; + } + } + + // load properties + var node:XML; + for each (node in source.properties) + custom = custom ? custom.extend(node) : new TmxPropertySet(node); + + //points/polygon/polyline + var nodes:XMLList = source.children(); + + for each(node in nodes) { + + shapeType = node.@name; + points = []; + var pointsArray:Array = String(node.@points).split(" "); + var len:uint = pointsArray.length; + + for (var i:uint = 0; i < len; ++i){ + var pstr:Array = pointsArray[i].split(","); + var point:Object = {x:int(pstr[0]), y:int(pstr[1])}; + points.push(point); + } + break; + } + } + } +} \ No newline at end of file diff --git a/src/citrus/utils/objectmakers/tmx/TmxObjectGroup.as b/src/citrus/utils/objectmakers/tmx/TmxObjectGroup.as new file mode 100644 index 00000000..99a1324a --- /dev/null +++ b/src/citrus/utils/objectmakers/tmx/TmxObjectGroup.as @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2010 by Thomas Jahn + * This content is released under the MIT License. + * Questions? Mail me at lithander at gmx.de! + ******************************************************************************/ +package citrus.utils.objectmakers.tmx { + + public class TmxObjectGroup { + + public var map:TmxMap; + public var name:String; + public var x:int; + public var y:int; + public var width:int; + public var height:int; + public var opacity:Number; + public var visible:Boolean; + public var properties:TmxPropertySet = null; + public var objects:Array = []; + + public function TmxObjectGroup(source:XML, parent:TmxMap) { + map = parent; + name = source.@name; + x = source.@x; + y = source.@y; + width = source.@width; + height = source.@height; + visible = !source.@visible || (source.@visible != 0); + opacity = source.@opacity; + + // load properties + var node:XML; + for each (node in source.properties) + properties = properties ? properties.extend(node) : new TmxPropertySet(node); + + // load objects + for each (node in source.object) + objects.push(new TmxObject(node, this)); + } + } +} \ No newline at end of file diff --git a/src/citrus/utils/objectmakers/tmx/TmxPropertySet.as b/src/citrus/utils/objectmakers/tmx/TmxPropertySet.as new file mode 100644 index 00000000..9230c9fd --- /dev/null +++ b/src/citrus/utils/objectmakers/tmx/TmxPropertySet.as @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2010 by Thomas Jahn + * This content is released under the MIT License. + * Questions? Mail me at lithander at gmx.de! + ******************************************************************************/ +package citrus.utils.objectmakers.tmx { + + public dynamic class TmxPropertySet { + + public function TmxPropertySet(source:XML) { + extend(source); + } + + public function extend(source:XML):TmxPropertySet { + for each (var prop:XML in source.property) { + var key:String = prop.@name; + var value:String = prop.@value; + this[key] = value; + } + return this; + } + } +} \ No newline at end of file diff --git a/src/citrus/utils/objectmakers/tmx/TmxTileSet.as b/src/citrus/utils/objectmakers/tmx/TmxTileSet.as new file mode 100644 index 00000000..612f17b5 --- /dev/null +++ b/src/citrus/utils/objectmakers/tmx/TmxTileSet.as @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2010 by Thomas Jahn + * This content is released under the MIT License. + * Questions? Mail me at lithander at gmx.de! + ******************************************************************************/ +package citrus.utils.objectmakers.tmx { + + import flash.display.BitmapData; + import flash.geom.Rectangle; + + public class TmxTileSet { + + private var _tileProps:Array = []; + private var _image:BitmapData = null; + + public var firstGID:int = 0; + public var map:TmxMap; + public var name:String; + public var tileWidth:int; + public var tileHeight:int; + public var spacing:int; + public var margin:int; + public var imageSource:String; + + // available only after immage has been assigned: + public var numTiles:int = 0xFFFFFF; + public var numRows:int = 1; + public var numCols:int = 1; + + public function TmxTileSet(source:XML, parent:TmxMap) { + firstGID = source.@firstgid; + + imageSource = source.image.@source; + + map = parent; + name = source.@name; + tileWidth = source.@tilewidth; + tileHeight = source.@tileheight; + spacing = source.@spacing; + margin = source.@margin; + + // read properties + for each (var node:XML in source.tile) + if (node.properties[0]) + _tileProps[int(node.@id)] = new TmxPropertySet(node.properties[0]); + } + + public function get image():BitmapData { + return _image; + } + + public function set image(v:BitmapData):void { + _image = v; + // TODO: consider spacing & margin + numCols = Math.floor(v.width / tileWidth); + numRows = Math.floor(v.height / tileHeight); + numTiles = numRows * numCols; + } + + public function hasGid(gid:int):Boolean { + return (gid >= firstGID) && (gid < firstGID + numTiles); + } + + public function fromGid(gid:int):int { + return gid - firstGID; + } + + public function toGid(id:int):int { + return firstGID + id; + } + + public function getPropertiesByGid(gid:int):TmxPropertySet { + return _tileProps[gid - firstGID]; + } + + public function getProperties(id:int):TmxPropertySet { + return _tileProps[id]; + } + + public function getRect(id:int):Rectangle { + // TODO: consider spacing & margin + return new Rectangle(((id-firstGID) % numCols) * tileWidth, Math.floor((id-firstGID) / numCols) * tileHeight, tileWidth, tileHeight); + } + } +} diff --git a/src/citrus/view/ACitrusCamera.as b/src/citrus/view/ACitrusCamera.as new file mode 100644 index 00000000..754a1a7b --- /dev/null +++ b/src/citrus/view/ACitrusCamera.as @@ -0,0 +1,624 @@ +package citrus.view { + + import aze.motion.EazeTween; + import citrus.core.CitrusEngine; + import citrus.math.MathUtils; + import citrus.math.MathVector; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + + + /** + * Citrus's camera. + */ + public class ACitrusCamera { + + /** + * Is the camera allowed to Zoom? + */ + protected var _allowZoom:Boolean = false; + + /** + * Is the camera allowed to Rotate? + */ + protected var _allowRotation:Boolean = false; + + /** + * the targeted rotation value. + */ + protected var _rotation:Number = 0; + + /** + * the targeted zoom value. + */ + protected var _zoom:Number = 1; + + /** + * base zoom - this is the overall zoom factor of the camera + */ + public var baseZoom:Number = 1; + + /** + * _aabb holds the axis aligned bounding box of the camera in rect + * and its relative position to it (with offsetX and offsetY) + */ + protected var _aabbData:Object = {offsetX:0, offsetY:0, rect:new Rectangle() }; + + /** + * ghostTarget is the eased position of target. + */ + protected var _ghostTarget:Point = new Point(); + + /** + * targetPos is used for calculating ghostTarget. + * (not sure if really necessary) + */ + protected var _targetPos:Point = new Point(); + + /** + * the _camProxy object is used as a container to hold the data to be applied to the _viewroot. + * it can be accessible publicly so that debugView can be correctly displaced, rotated and scaled as _viewroot will be. + */ + protected var _camProxy:Object = { x: 0, y: 0, offset:new Point(), scale: 1, rotation: 0 }; + + /** + * projected camera position + offset. (used internally) + */ + protected var _camPos:Point = new Point(); + + /** + * the ease factor for zoom + */ + public var zoomEasing:Number = 0.05; + + /** + * the ease factor for rotation + */ + public var rotationEasing:Number = 0.05; + + protected var _viewRoot:*; + + // Camera properties + /** + * The thing that the camera will follow if a manual position is not set. + */ + protected var _target:Object; + + /** + * The camera position to be set manually if target is not set. + */ + protected var _manualPosition:Point; + + + protected var _callOnUpdateQueue:Vector. = new Vector.(); + + /** + * decides wether the camera will be updated by citrus engine. + * If you use the camera only for multi resolution purposes or for 'non moving' states, + * you may disable the camera to save some performances. In such cases, you may still call + * reset() in the state's initialize() so that the camera will set itself up at the right position/zoom/rotation. + */ + public var enabled:Boolean = false; + + /** + * This defines the camera "center" position as a factor of the camera lens dimensions. + * x and y components will be multiplied to cameraLensWidth/cameraLensHeight + * to determine the position of the camera center. + * values must be between 0 and 1. + */ + public var center:Point = new Point(0.5, 0.5); + + /** + * real camera center position + */ + protected var offset:Point = new Point(); + + /** + * A value between 0 and 1 that specifies the speed at which the camera catches up to the target. + * 0 makes the camera not follow the target at all and 1 makes the camera follow the target exactly. + */ + public var easing:Point = new Point(0.25, 0.05); + + /** + * A rectangle specifying the minimum and maximum area that the camera is allowed to follow the target in. + */ + public var bounds:Rectangle; + + /** + * defines a zone in the camera space where target will be able to move without the camera following it. + * left to its default value (0,0,0,0) the camera will constantly try to move/ease to the target. + * if set to 0,0,100,100, the target has to move 50px left or 50px right (in camera space) for horizontal tracking to start, + * the same vertically. + * + * the deadZone's rectangle x and y values are not used. + */ + public var deadZone:Rectangle = new Rectangle(); + + /** + * The width of the visible game screen. This will usually be the same as your stage width unless your game has a border. + */ + public var cameraLensWidth:Number; + + public var followTarget:Boolean = true; + + /** + * The height of the visible game screen. This will usually be the same as your stage width unless your game has a border. + */ + public var cameraLensHeight:Number; + + /** + * helper matrix for transformation + */ + protected var _m:Matrix = new Matrix(); + + /** + * helper point + */ + protected var _p:Point = new Point(); + + /** + * helper rectangle + */ + protected var _r:Rectangle = new Rectangle(); + + /** + * camera rectangle + */ + protected var _rect:Rectangle = new Rectangle(); + + /** + * helper object for bounds checking + */ + protected var _b:Object = { w2:0, h2:0, diag2:0, rotoffset:new Point(), br:0, bl:0, bt:0, bb:0 }; + + /** + * this mode will force the camera (and its 'content') to be contained within the bounds. + * zoom will be restricted - and recalculated if required. + * this restriction is based on the camera's AABB rectangle,you will never see anything out of the bounds. + * actually makes the camera 'hit' the bounds, the camera will be displaced to prevent it. + */ + public static const BOUNDS_MODE_AABB:String = "BOUNDS_MODE_AABB"; + + /** + * this mode will force the offset point of the camera to stay within the bounds (whatever the zoom and rotation are) + * things can be seen outside of the bounds, but there's no zoom recalculation or camera displacement when rotating and colliding with the bounds + * unlike the other mode. + */ + public static const BOUNDS_MODE_OFFSET:String = "BOUNDS_MODE_OFFSET"; + + /** + * This mode is a mix of the two other modes : + * The camera offset point is now contained inside inner bounds which allows to never see anything outside of the level + * like the AABB mode, but unlike the AABB mode, when rotating, the camera doesn't collide with borders as the inner bounds + * sides are distant from their correspoding bounds sides from the camera's half diagonal length : + * this means the camera can freely rotate in a circle, and that circle cannot go out of the defined bounds. + * this also means the corners of the bounded area will never be seen. + */ + public static const BOUNDS_MODE_ADVANCED:String = "BOUNDS_MODE_ADVANCED"; + + /** + * how camera movement should be allowed within the defined bounds. + * defaults to ACitrusCamera.BOUNDS_MODE_AABB + */ + public var boundsMode:String = BOUNDS_MODE_AABB; + + /** + * the parallaxed objects are based on (0,0) of the level. + * this is how parallax has been applied since the beginning of CE. + */ + public static const PARALLAX_MODE_TOPLEFT:String = "PARALLAX_MODE_TOPLEFT"; + + /** + * parallaxed objects are 'displaced' according to their parallax value from the center of the camera, + * giving a perspective/fake depth effect where the vanishing point is the center of the camera. + */ + public static const PARALLAX_MODE_DEPTH:String = "PARALLAX_MODE_DEPTH"; + + /** + * defines the way parallax is applied to objects position. + * the default is PARALLAX_MODE_TOPLEFT. + */ + public var parallaxMode:String = PARALLAX_MODE_TOPLEFT; + + protected var _ce:CitrusEngine; + + public function ACitrusCamera(viewRoot:*) { + + _viewRoot = viewRoot; + initialize(); + } + + /** + * Override this function to change the way camera lens dimensions are calculated + * or to set other inital properties for the camera type. + */ + protected function initialize():void { + + _ce = CitrusEngine.getInstance(); + cameraLensWidth = _ce.screenWidth; + cameraLensHeight = _ce.screenHeight; + + _ce.onStageResize.add(onResize); + } + + protected function onResize(w:Number, h:Number):void + { + cameraLensWidth = _ce.screenWidth; + cameraLensHeight = _ce.screenHeight; + } + + /** + * This is a non-critical helper function that allows you to quickly set available camera properties in one place. + * if center and easing are set to null, the default values are used. + * @param target object with x and y properties that will be tracked by the camera + * @param center values between 0 and 1 - x/y components will be multiplied to the cameraLensWidth/cameraLensHeight value to determine the position of the camera center. + * @param bounds rectangle that determines the area the camera is allowed to move in + * @param easing values between 0 and 1 - that specifies by how much distance from the target the camera should move on each update + * @return this The Instance of the ACitrusCamera. + */ + public function setUp(target:Object,bounds:Rectangle = null, center:Point = null , easing:Point = null):ACitrusCamera + { + if (target) + { + this.target = target; + _ghostTarget.x = target.x; + _ghostTarget.y = target.y; + } + if (center) + { + if (center.x > 1) center.x = 1; + if (center.x < 0) center.x = 0; + if (center.y > 1) center.y = 1; + if (center.y < 0) center.y = 0; + + this.center = center; + } + if (bounds) + this.bounds = bounds; + if (easing) + this.easing = easing; + + enabled = true; + return this; + } + + /** + * sets camera transformation with no easing + * by setting all easing values to 1 temporarily and updating camera once. + * can be called at the beginning of a state to prevent camera effects then. + */ + public function reset():void + { + var tmp1:Point = easing.clone(); + var tmp2:Number = rotationEasing; + var tmp3:Number = zoomEasing; + + rotationEasing = 1; + zoomEasing = 1; + easing.setTo(1, 1); + + update(); + + easing.copyFrom(tmp1); + rotationEasing = tmp2; + zoomEasing = tmp3; + } + + /** + * Moves from the current target to the newTarget at a linear speed, sets the camera's target to be the newTarget + * then calls onComplete. + * @param newTarget any object with x/y properties + * @param speed by how much should the camera move towards the new target on each frame? + * @param onComplete + */ + public function switchToTarget(newTarget:Object, speed:Number = 10, onComplete:Function = null):void + { + trace(camPos.x, camPos.y); + var moveTarget:Point = new Point(camPos.x,camPos.y); + var vec:MathVector = new MathVector(0, 0); + + var oldEasing:Point = easing.clone(); + easing.setTo(1, 1); + + var oldDeadZone:Rectangle = deadZone.clone(); + deadZone.setTo(0, 0, 0, 0); + + target = moveTarget; + + _callOnUpdateQueue.push({ + func:switchToTargetUpdate, + args: { newTarget:newTarget, speed:speed, onComplete:onComplete, moveTarget:moveTarget, vec:vec, oldEasing:oldEasing, oldDeadZone:oldDeadZone } + }); + } + + + protected function switchToTargetUpdate(args:Object):Boolean + { + args.vec.setTo(args.newTarget.x - args.moveTarget.x, args.newTarget.y - args.moveTarget.y); + if(args.vec.length > args.speed) + args.vec.length = args.speed; + + args.moveTarget.x += args.vec.x; + args.moveTarget.y += args.vec.y; + + if (MathUtils.DistanceBetweenTwoPoints(args.newTarget.x,args.moveTarget.x,args.newTarget.y,args.moveTarget.y) <= 0.1) + { + target = args.newTarget; + easing = args.oldEasing; + deadZone = args.oldDeadZone; + if (args.onComplete != null) + args.onComplete(); + return true; + } + + return false; + } + + /** + * Moves from current target to newTarget using EazeTween. + * function returns the EazeTween instance created. + * @param newTarget any object with x/y properties + * @param duration in seconds + * @param easingFunction with the f(x) = y format + * @param onComplete callback when the tween ends + * @return EazeTween + */ + public function tweenSwitchToTarget(newTarget:Object, duration:Number = 2, easingFunction:Function = null, onComplete:Function = null):EazeTween + { + var moveTarget:Point = new Point(camPos.x,camPos.y); + + var oldEasing:Point = easing.clone(); + easing.setTo(1, 1); + + var oldDeadZone:Rectangle = deadZone.clone(); + deadZone.setTo(0, 0, 0, 0); + + target = moveTarget; + + var eaze:EazeTween = new EazeTween(moveTarget, false).to(duration, { x:newTarget.x, y:newTarget.y } ).onComplete(function():void + { + target = newTarget; + easing = oldEasing; + deadZone = oldDeadZone; + if (onComplete != null) + onComplete(); + }); + + if (easingFunction != null) + eaze.easing(easingFunction); + + eaze.start(); + return eaze; + } + + public function zoom(factor:Number):void { + throw(new Error("Warning: " + this + " cannot zoom.")); + } + + /** + * fits a defined area within the camera lens dimensions. + * Similar to fitting a rectangle inside another rectangle by multiplying its size, + * therefore keeping its aspect ratio. the factor used to fit is returned + * and set as the current target zoom factor. + * + * if storeInBaseZoom is set to true, then the calculated ratio is stored in the camera's baseZoom + * and from now, all zoom will be relative to that ratio (baseZoom is 1 by default and multiplied + * to every zoom operations you do using the camera methods) - this helps create relative zoom effects + * while keeping a base zoom when zooming at 1 where the camera would still fit the area you decided : + * specially usefull for multi resolution handling. + * @param width width of the area to fit inside the camera lens dimensions. + * @param height height of the area to fit inside the camera lens dimensions. + * @param storeInBaseZoom , whether to store the ratio into baseZoom or not. + * @return calculated zoom ratio + */ + public function zoomFit(width:Number, height:Number, storeInBaseZoom:Boolean = false):Number { + throw(new Error("Warning: " + this + " cannot zoomFit.")); + } + + public function rotate(angle:Number):void { + throw(new Error("Warning: " + this + " cannot rotate.")); + } + + public function setRotation(angle:Number):void { + throw(new Error("Warning: " + this + " cannot rotate.")); + } + + public function setZoom(factor:Number):void { + throw(new Error("Warning: " + this + " cannot zoom.")); + } + + public function getZoom():Number { + throw(new Error("Warning: " + this + " cannot zoom.")); + } + + public function getRotation():Number { + throw(new Error("Warning: " + this + " cannot rotate.")); + } + + /** + * Update the camera. + */ + public function update():void { + if (_callOnUpdateQueue.length > 0) + for (var k:String in _callOnUpdateQueue) { + if ((_callOnUpdateQueue[k].func)(_callOnUpdateQueue[k].args)) + _callOnUpdateQueue.splice(k as int, 1); + } + } + + public function destroy():void { + _callOnUpdateQueue.length = 0; + _ce.onStageResize.remove(onResize); + } + + /* + * Getters and setters + */ + + /** + * object with x and y properties that will be tracked by the camera + */ + public function set target(o:Object):void { + _manualPosition = null; + _target = o; + } + public function get target():Object { + return _target; + } + + /** + * the camera center position in state coordinates + */ + public function get camPos():Point { + return _camPos; + } + + public function set manualPosition(p:Point):void { + _target = null; + _manualPosition = p; + } + + public function get manualPosition():Point { + return _manualPosition; + } + + /** + * list of functions/arguments to run in update call for camera sync. + * object structure : {func:Function, args:Object} + * function should return a boolean, if it returns true, the object is removed from the list. + */ + public function get callOnUpdateQueue():Vector. + { + return _callOnUpdateQueue; + } + + public function set allowRotation(value:Boolean):void { + throw(new Error("Warning: " + this + " cannot rotate.")); + } + + public function set allowZoom(value:Boolean):void { + throw(new Error("Warning: " + this + " cannot zoom.")); + } + + public function get allowZoom():Boolean { + throw(new Error("Warning: " + this + " cannot zoom.")); + } + + public function get allowRotation():Boolean { + throw(new Error("Warning: " + this + " cannot rotate.")); + } + + /** + * camProxy is read only. + * contains the data to be applied to container layers (_viewRoot and debug views). + */ + public function get camProxy():Object { + return _camProxy; + } + + /** + * read-only to get the eased position of the target, which is the actual point the camera + * is looking at ( - the offset ) + */ + public function get ghostTarget():Point { + return _ghostTarget; + } + + /** + * zoom with base factor + */ + protected function get mzoom():Number { + return _zoom * baseZoom; + } + + protected function set mzoom(val:Number):void { + _zoom = val / baseZoom; + } + + /** + * This is the transform matrix the camera applies to the state viewroot. + * it is also applied to the physics debug view. + */ + public function get transformMatrix():Matrix + { + return _m; + } + + /** + * Check is the given coordinates in State space are contained within the camera. + * + * set the area argument to define a different area of the screen, for example if you want to check + * further left/right/up/down than the camera's default rectangle which is : (0,0,cameraLensWidth,cameraLensHeight) + */ + public function contains(xa:Number,ya:Number,area:Rectangle = null):Boolean + { + _p.setTo(xa, ya); + + if(!area) + _rect.setTo(0, 0, cameraLensWidth, cameraLensHeight); + else + _rect.copyFrom(area); + + _p.copyFrom(_m.transformPoint(_p)); + + return _rect.contains(_p.x, _p.y); + } + + /** + * Check is the given rectangle is fully contained within the camera. + * will return false even if partially visible, collision with borders included. + * + * The rectangle *must* be in the same space as the camera's rectangle, this means in the starling stage if in a StarlingState, + * or the flash native stage if in a normal State. + * + * set the area argument to define a different area of the screen, for example if you want to check + * further left/right/up/down than the camera's default rectangle which is : (0,0,cameraLensWidth,cameraLensHeight) + */ + public function containsRect(rectangle:Rectangle, area:Rectangle = null):Boolean + { + _p.setTo(rectangle.x + rectangle.width * .5, rectangle.y + rectangle.height * .5); + + if(!area) + _rect.setTo(0, 0, cameraLensWidth, cameraLensHeight); + else + _rect.copyFrom(area); + + _r.setTo(_p.x - rectangle.width * .5, _p.y - rectangle.height * .5, rectangle.width, rectangle.height); + return _rect.containsRect(_r); + } + + /** + * Check is the given rectangle intersects with the camera rectangle. + * (if its partially visible, true will be returned. + * + * The rectangle *must* be in the same space as the camera's rectangle, this means in the starling stage if in a StarlingState, + * or the flash native stage if in a normal State. + * + * set the area argument to define a different area of the screen, for example if you want to check + * further left/right/up/down than the camera's default rectangle which is : (0,0,cameraLensWidth,cameraLensHeight) + */ + public function intersectsRect(rectangle:Rectangle, area:Rectangle = null):Boolean + { + _p.setTo(rectangle.x + rectangle.width * .5, rectangle.y + rectangle.height * .5); + + if(!area) + _rect.setTo(0, 0, cameraLensWidth, cameraLensHeight); + else + _rect.copyFrom(area); + + _r.setTo(_p.x - rectangle.width * .5, _p.y - rectangle.height * .5, rectangle.width, rectangle.height); + return _rect.intersects(_r); + } + + /** + * returns the camera's axis aligned bounding rectangle in State space. + */ + public function getRect():Rectangle + { + return _aabbData.rect; + } + + } +} diff --git a/src/citrus/view/ACitrusView.as b/src/citrus/view/ACitrusView.as new file mode 100644 index 00000000..bd0c7211 --- /dev/null +++ b/src/citrus/view/ACitrusView.as @@ -0,0 +1,160 @@ +package citrus.view { + + import citrus.core.CitrusObject; + import citrus.utils.LoadManager; + + import flash.utils.Dictionary; + + /** + * This is an abstract class that is extended by any view managers, such as the SpriteView. It provides default properties + * and functionality that all game views need, such as camera controls, parallaxing, and graphics object displaying and management. + * + *

    This is the class by which you will grab a reference to the graphical representations of your Citrus Objects, + * which will be useful if you need to add mouse event handlers to them, or add graphics effects and filter.

    + * + *

    The CitrusView was meant to be extended to support multiple rendering methods, such as blitting, or even Stage3D thanks to Starling and Away3D. + * The goal is to provide as much decoupling as possible of the data/logic from the view.

    + */ + public class ACitrusView + { + /** + * This is the manager object that keeps track of the asynchronous load progress of all graphics objects that are loading. + * You will want to use the load manager's bytesLoaded and bytesTotal properties to monitor when your state's graphics are + * completely loaded and ready for revealing. + * + *

    Normally, you will want to hide your state from the player's view until the load manager dispatches its onComplete event, + * notifying you that all graphics have been loaded. This is the object that you will want to reference in your loading screens. + *

    + */ + public var loadManager:LoadManager; + + public var camera:ACitrusCamera; + + protected var _viewObjects:Dictionary = new Dictionary(); + protected var _root:*; + protected var _viewInterface:Class; + + /** + * There is one CitrusView per state, so when a new state is initialized, it creates the view instance. + * You can override which type of CitrusView you would like to create via the State.createView() protected method. + * Thanks to the State class, you have access to traditional flash display list, blitting and Away3D. + * If you want to target Starling you have to use the StarlingState class. + */ + public function ACitrusView(root:*, viewInterface:Class) + { + _root = root; + _viewInterface = viewInterface; + + loadManager = new LoadManager(); + } + + public function destroy():void + { + camera.destroy(); + loadManager.destroy(); + } + + /** + * This should be implemented by a CitrusView subclass. The update method's job is to iterate through all the CitrusObjects, + * and update their graphical counterparts on every frame. See the SpriteView's implementation of the update() method for + * specifics. + */ + public function update(timeDelta:Number):void + { + } + + /** + * The active state automatically calls this method whenever a new CitrusObject is added to it. It uses the CitrusObject + * to create the appropriate graphical representation. It also tells the LoadManager to begin listening to Loader events + * on the graphics object. + */ + public function addArt(citrusObject:Object):void + { + if (!(citrusObject is _viewInterface)) + return; + + var art:Object = createArt(citrusObject); + + if (art) + _viewObjects[citrusObject] = art; + + if (art["content"] == null) + loadManager.add(art, citrusObject as CitrusObject); + + } + + /** + * This is called by the active state whenever a CitrusObject is removed from the state, effectively also removing the + * art representation. + */ + public function removeArt(citrusObject:Object):void + { + if (!(citrusObject is _viewInterface)) + return; + + destroyArt(citrusObject); + delete _viewObjects[citrusObject]; + } + + /** + * Gets the graphical representation of a CitrusObject that is being managed by the active state's view. + * This is the method that you will want to call to get the art for a CitrusObject. + * + *

    For instance, if you want to perform an action when the user clicks an object, you will want to call + * this method to get the MovieClip that is associated with the CitrusObject that you are listening for a click upon. + *

    + */ + public function getArt(citrusObject:Object):Object + { + if (!citrusObject is _viewInterface) + throw new Error("The object " + citrusObject + " does not have a graphical counterpart because it does not implement " + _viewInterface + "."); + + return _viewObjects[citrusObject]; + } + + /** + * Gets a reference to the CitrusObject associated with the provided art object. + * This is useful for instances such as when you need to get the CitrusObject for a graphic that got clicked on or otherwise interacted with. + * @param art The graphical object that represents the CitrusObject you want. + * @return The CitrusObject associated with the provided art object. + */ + public function getObjectFromArt(art:Object):Object + { + for (var object:Object in _viewObjects) + { + if (_viewObjects[object] == art) + return object; + } + return null; + } + + /** + * A CitrusView subclass will extend this method to provide specifics on how to create the graphical representation of a CitrusObject. + * @param citrusObject The object for which to create the art. + * @return The art object. + * + */ + protected function createArt(citrusObject:Object):Object + { + return null; + } + + /** + * A CitrusView subclass will extend this method to update the graphical representation for each CitrusObject. + * @param citrusObject A CitrusObject whose graphical counterpart needs to be updated. + * @param art The graphics object that will be updated based on the provided CitrusObject. + */ + protected function updateArt(citrusObject:Object, art:Object):void + { + + } + + /** + * A CitrusView subclass will extend this method to destroy the art associated with the provided CitrusObject. + */ + protected function destroyArt(citrusObject:Object):void + { + + } + } +} \ No newline at end of file diff --git a/src/citrus/view/ICitrusArt.as b/src/citrus/view/ICitrusArt.as new file mode 100644 index 00000000..0ad23bed --- /dev/null +++ b/src/citrus/view/ICitrusArt.as @@ -0,0 +1,13 @@ +package citrus.view +{ + + public interface ICitrusArt + { + + function get updateArtEnabled():Boolean; + function set updateArtEnabled(val:Boolean):void; + function update(stateView:ACitrusView):void; + + } + +} \ No newline at end of file diff --git a/src/citrus/view/ISpriteView.as b/src/citrus/view/ISpriteView.as new file mode 100644 index 00000000..57417240 --- /dev/null +++ b/src/citrus/view/ISpriteView.as @@ -0,0 +1,172 @@ +package citrus.view +{ + /** + * The ISpriteView interface provides a common interface between a CitrusObject and the SpriteView view manager. + * All objects that need to have graphical representations on screen need to implement this, if your + * objects are in a state that uses the CitrusView as its view (most common). Often, especially + * when working with Box2D, game object units will be different than than view object units. + * In Box2D, units are in meters, but graphics are rendered in pixels. + * Citrus Engine does not put a requirement on whether the game logic or the view manager should + * perform the conversion. + * If you desire the game logic to perform the unit conversion, the values should be multiplied by + * [commonly] 30 before being returned in order to convert the meter values to pixel values. + */ + public interface ISpriteView + { + /** + * called when the art is created (and loaded if loading is required) + * @param citrusArt the art + */ + function handleArtReady(citrusArt:ICitrusArt):void; + + /** + * called when the art changes. the argument is the art with its previous content + * so that you can remove event listeners from it for example. + * @param citrusArt the art + */ + function handleArtChanged(oldArt:ICitrusArt):void; + + /** + * the body used by a physics engine + */ + function getBody():*; + + /** + * The x position of the object. + */ + function get x():Number; + + /** + * The y position of the object. + */ + function get y():Number; + + /** + * The z position of the object. + */ + function get z():Number; + + /** + * The width of the object. + */ + function get width():Number; + + /** + * The height of the object. + */ + function get height():Number; + + /** + * The depth of the object (used for 3D content). + */ + function get depth():Number; + + /** + * The velocity of the object. + */ + function get velocity():Array; + + /** + * The ratio at which the object scrolls in relation to the camera on the x axis. + */ + function get parallaxX():Number; + + /** + * The ratio at which the object scrolls in relation to the camera on the y axis. + */ + function get parallaxY():Number; + + /** + * The rotation value of the object. + *

    Commonly, flash uses degrees to display art rotation, but game logic is usually in radians. + * If a conversion is necessary and you choose the game object to perform the conversion rather than + * the view manager, then you will want to perform your conversion here.

    + */ + function get rotation():Number; + + /** + * The group property specifies the depth sorting. Objects placed in group 1 will be behind objects placed in group 2. + * Note that groups and parallax are unrelated, so be careful not to have an object have a lower parallax value than an object + * in a group below it. + */ + function get group():uint; + + /** + * The visibility of the object. + */ + function get visible():Boolean; + + /** + * Turn it to true if you want to be able to interact with touch/mouse on the object. + */ + function get touchable():Boolean; + + /** + * This is where you specify what your graphical representation of your CitrusObject will be. + * + *

    You can specify your view value in multiple ways:

    + * + *

    If you want your graphic to be a SWF, PNG, or JPG that + * is loaded at runtime, then assign view a String URL relative to your game's SWF, just like you would + * if you were loading any file in Flash. (graphic = "graphics/Hero.swf")

    + * + *

    If your graphic is embedded into the SWF, you can assign the view property in two ways: Either by package string + * notation (view = "com.myGame.MyHero"), or by using a direct class reference (graphic = MyHero). The first method, String notation, is useful + * when you are using a level editor such as the Flash IDE or GLEED2D because all data must come through in String form. However, if you + * are hardcoding your graphic class, you can simply pass a direct reference to the class. + * Whichever way you specify your class, your class must be (on some level) a DisplayObject.

    + * + *

    You can specify your view as an instance of a display object depending of your view renderer.

    + * + *

    If you are using a level editor and using the ObjectMaker to batch-create your + * CitrusObjects, you will need to specify the entire classpath in string form and let the factory turn your string + * into an actual class. Also, the class definition (MyHeroGraphic, for example) will need to be compiled into your code + * somewhere, otherwise the game will not be able to get the class definition from a String.

    + * + *

    If your graphic is an external file such as a PNG, JPG, or SWF, you can provide the path to the file (either an absolute path, + * or a relative path from your HTML file or SWF). The SpriteView will detect that it is an external file and + * load the file using the LoadManager class.

    + */ + function get view():*; + + /** + * The object's associated art (Away3DArt, SpriteArt or StarlingArt) + * will be set only when the art is ready (loaded/created.) and after being added to the state. + */ + function get art():ICitrusArt; + + /** + * A string representing the current animation state that your object is in, such as "run", "jump", "attack", etc. + * The SpriteView checks this property every frame and, if your graphic is a SWF, attempts to "gotoAndPlay()" to a + * label with the name of the animation property. + * + * If you want your graphic to not loop, you should call stop() on the last frame of your animation from within your SWF file. + */ + function get animation():String; + + /** + * If true, the view will invert your graphic. This is common in side-scrolling games so that you don't have to draw + * right-facing and left-facing versions of all your graphics. If you are using the inverted property to invert your + * graphics, make sure you set your registration to "center" or the graphic will flip like a page turning instead of a card + * flipping. + */ + function get inverted():Boolean; + + /** + * The x offset from the graphic's registration point. + */ + function get offsetX():Number; + + /** + * The y offset from the graphic's registration point. + */ + function get offsetY():Number; + + /** + * Specify either "topLeft" or "center" to position your graphic's registration. Please note that this is + * only useful for graphics that are loaded dynamically at runtime (PNGs, SWFs, and JPGs). If you are embedding + * your art, you should handle the registration in your embedded class. + */ + function get registration():String; + } +} \ No newline at end of file diff --git a/src/citrus/view/starlingview/AnimationSequence.as b/src/citrus/view/starlingview/AnimationSequence.as new file mode 100644 index 00000000..bec051a4 --- /dev/null +++ b/src/citrus/view/starlingview/AnimationSequence.as @@ -0,0 +1,280 @@ +package citrus.view.starlingview { + + import citrus.core.CitrusEngine; + import citrus.core.starling.StarlingCitrusEngine; + + import starling.display.MovieClip; + import starling.display.Sprite; + import starling.events.Event; + import starling.extensions.textureAtlas.DynamicAtlas; + import starling.textures.TextureAtlas; + + import org.osflash.signals.Signal; + + import flash.display.MovieClip; + import flash.utils.Dictionary; + + /** + * The Animation Sequence class represents all object animations in one sprite sheet. You have to create your texture atlas in your state class. + * Example : var hero:Hero = new Hero("Hero", {x:400, width:60, height:130, view:new AnimationSequence(textureAtlas, ["walk", "duck", "idle", "jump"], "idle")}); + * Important: for managing if an animation should loop, you've to set it up at StarlingArt.setLoopAnimations(["fly", "fallen"]). By default, the walk's + * animation is the only one looping. + */ + public class AnimationSequence extends Sprite { + + /** + * The signal is dispatched each time an animation is completed, giving the animation name as argument. + */ + public var onAnimationComplete:Signal; + + private var _ce:StarlingCitrusEngine; + private var _textureAtlas:*; + private var _animations:Array; + private var _firstAnimation:String; + private var _animFps:Number; + private var _firstAnimLoop:Boolean; + private var _smoothing:String; + + private var _mcSequences:Dictionary; + private var _previousAnimation:String; + + /** + * @param textureAtlas a TextureAtlas or an AssetManager object with your object's animations you would like to use. + * @param animations an array with the object's animations as a String you would like to pick up. + * @param firstAnimation a string of your default animation at its creation. + * @param animFps a number which determines the animation MC's fps. + * @param firstAnimLoop a boolean, set it to true if you want your first animation to loop. + * @param smoothing a string indicating the smoothing algorithms used for the AnimationSequence, default is bilinear. + */ + public function AnimationSequence(textureAtlas:*, animations:Array, firstAnimation:String, animFps:Number = 30, firstAnimLoop:Boolean = false, smoothing:String = "bilinear") { + + super(); + + _ce = CitrusEngine.getInstance() as StarlingCitrusEngine; + + onAnimationComplete = new Signal(String); + + _textureAtlas = textureAtlas; + _animations = animations; + _firstAnimation = firstAnimation; + _animFps = animFps; + _firstAnimLoop = firstAnimLoop; + _smoothing = smoothing; + + _mcSequences = new Dictionary(); + + addTextureAtlasWithAnimations(_textureAtlas, _animations); + + addChild(_mcSequences[_firstAnimation]); + _ce.juggler.add(_mcSequences[_firstAnimation]); + _mcSequences[_firstAnimation].loop = _firstAnimLoop; + + _previousAnimation = _firstAnimation; + } + + /** + * It may be useful to add directly a MovieClip instead of a Texture Atlas to enable its manipulation like an animation's reversion for example. + * Be careful, if you clone the AnimationSequence it's not taken into consideration. + * @param mc a MovieClip you would like to use. + * @param animation the object's animation name as a String you would like to pick up. + */ + public function addMovieClip(mc:starling.display.MovieClip, animation:String):void { + + if ((_mcSequences[animation])) + throw new Error(this + " already have the " + animation + " animation set up in its animations' array"); + + _mcSequences[animation] = mc; + _mcSequences[animation].name = animation; + _mcSequences[animation].addEventListener(Event.COMPLETE, _animationComplete); + _mcSequences[animation].smoothing = _smoothing; + _mcSequences[animation].fps = _animFps; + } + + /** + * If you need more than one TextureAtlas for your character's animations, use this function. + * Be careful, if you clone the AnimationSequence it's not taken into consideration. + * @param textureAtlas a TextureAtlas object with your object's animations you would like to use. + * @param animations an array with the object's animations as a String you would like to pick up. + */ + public function addTextureAtlasWithAnimations(textureAtlas:*, animations:Array):void { + + for each (var animation:String in animations) { + + if (textureAtlas.getTextures(animation).length == 0) + throw new Error(textureAtlas + " doesn't have the " + animation + " animation in its TextureAtlas"); + + _mcSequences[animation] = new starling.display.MovieClip(textureAtlas.getTextures(animation), _animFps); + + _mcSequences[animation].name = animation; + _mcSequences[animation].addEventListener(Event.COMPLETE, _animationComplete); + _mcSequences[animation].smoothing = _smoothing; + } + } + + /** + * You may want to remove animations from the AnimationSequence, use this function. + * Be careful, if you clone the AnimationSequence it's not taken into consideration. + * @param animations an array with the object's animations as a String you would like to remove. + */ + public function removeAnimations(animations:Array):void { + + for each (var animation:String in animations) { + + if (!(_mcSequences[animation])) + throw new Error(this.parent.name + " doesn't have the " + animation + " animation set up in its animations' array"); + + _mcSequences[animation].removeEventListener(Event.COMPLETE, _animationComplete); + _mcSequences[animation].dispose(); + + delete _mcSequences[animation]; + } + } + + public function removeAllAnimations():void + { + removeAnimations(_animations); + } + + /** + * Called by StarlingArt, managed the MC's animations. If your object is a CitrusObject you should + * manage its animation via object's animation variable. + * @param animation the MC's animation + * @param animLoop true if the MC is a loop + */ + public function changeAnimation(animation:String, animLoop:Boolean):void { + + if (!(_mcSequences[animation])) + throw new Error(this.parent.name + " doesn't have the " + animation + " animation set up in its animations' array"); + + removeChild(_mcSequences[_previousAnimation]); + _ce.juggler.remove(_mcSequences[_previousAnimation]); + + addChild(_mcSequences[animation]); + _ce.juggler.add(_mcSequences[animation]); + _mcSequences[animation].loop = animLoop; + _mcSequences[animation].currentFrame = 0; + + _previousAnimation = animation; + } + + /** + * Called by StarlingArt, remove or add to the Juggler if the Citrus Engine is playing or not. + */ + public function pauseAnimation(value:Boolean):void { + + value ? _ce.juggler.add(_mcSequences[_previousAnimation]) : _ce.juggler.remove(_mcSequences[_previousAnimation]); + } + + public function destroy():void { + + onAnimationComplete.removeAll(); + + removeChild(_mcSequences[_previousAnimation]); + _ce.juggler.remove(_mcSequences[_previousAnimation]); + + removeAllAnimations(); + + _mcSequences = null; + } + + /** + * A dictionary containing all animations registered thanks to their string name. + */ + public function get mcSequences():Dictionary { + return _mcSequences; + } + + /** + * creates an AnimationSequence from a flash movie clip + * different animations should be in separate flash movie clips, + * each should have their name set to whatever animation they represent. + * all of those moviesclips should be added as children,in the first frame, + * to the movie clip provided as an argument to this function so that + * DynamicAtlas will run through each children, create animations for each + * with their name as animation names to be used in the AnimationSequence that gets returned. + * For more info, check out the Dynamic Texture Atlas Extension and how it renders texture atlases. + * @param swf flash movie clip instance containing instances of movie clip animations + * @param firstAnim the name of the first animation to be played + * @param animFps fps of the AnimationSequence + * @param firstAnimLoop should the first animation loop? + * @param smoothing + * @return + */ + public static function fromMovieClip(swf:flash.display.MovieClip,firstAnim:String = null, animFps:int = 30, firstAnimLoop:Boolean = true, smoothing:String = "bilinear"):AnimationSequence + { + var textureAtlas:TextureAtlas = DynamicAtlas.fromMovieClipContainer(swf, (CitrusEngine.getInstance() as StarlingCitrusEngine).scaleFactor, 0, true, true); + var textureAtlasNames:Vector. = textureAtlas.getNames(); + + var sorter:Object = { }; + + for each (anim in textureAtlasNames) + { + anim = anim.split("_")[0]; + if (!(anim in sorter)) + sorter[anim] = true; + } + + var anims:Array = []; + var anim:String; + + for (anim in sorter) + anims.push(anim); + + return new AnimationSequence(textureAtlas, anims,(firstAnim in sorter)? firstAnim : anims[0], animFps, firstAnimLoop,smoothing); + } + + /** + * returns a vector of all animation names in this AnimationSequence. + */ + public function getAnimationNames():Vector.{ + var names:Vector. = new Vector.(); + var name:String; + for (name in _mcSequences) + names.push(name); + return names; + } + + /** + * Return a clone of the current AnimationSequence. Animations added via addMovieClip or addTextureAtlasWithAnimations aren't included. FPS settings added via setAnimFps aren't included too. + */ + public function clone():AnimationSequence { + return new AnimationSequence(_textureAtlas, _animations, _firstAnimation, _animFps, _firstAnimLoop, _smoothing); + } + + /** + * Set the fps for animations individually. + * @param animations an array with the object's animations as a String you would like to pick up. + * @param animFps an array of numbers which determine the animation MC's fps. + */ + public function setAnimFps(animations:Array, animFps:Array):void + { + var numberOfAnimations:uint = animations.length; + var numberOfFpsSettings:uint = animFps.length; + + // check the amount of the animation names and fps values + if (numberOfAnimations < 1 || numberOfFpsSettings < 1 || numberOfAnimations != numberOfFpsSettings) + throw new Error(this + " invalid input - animations: " + numberOfAnimations + ", fps settings: " + numberOfFpsSettings); + + for (var i:uint = 0; i < numberOfAnimations; i++) + { + if (typeof(animations[i]) != "string") { + throw new Error(this + " the animation-name " + animations[i] + " is set as " + typeof(animations[i]) + " instead of string"); + } + + if (typeof(animFps[i]) != "number") { + throw new Error(this + " the fps setting " + animFps[i] + " is set as " + typeof(animFps[i]) + " instead of number"); + } + + if (!(_mcSequences[animations[i]])) + throw new Error(this + " the " + animations[i] + " animation hasn't been set up"); + + // set the fps for the animation + _mcSequences[animations[i]].fps = animFps[i]; + } + } + + private function _animationComplete(evt:Event):void { + onAnimationComplete.dispatch((evt.target as starling.display.MovieClip).name); + } + } +} diff --git a/src/citrus/view/starlingview/StarlingArt.as b/src/citrus/view/starlingview/StarlingArt.as new file mode 100644 index 00000000..6e6d6919 --- /dev/null +++ b/src/citrus/view/starlingview/StarlingArt.as @@ -0,0 +1,530 @@ +package citrus.view.starlingview { + import citrus.core.CitrusEngine; + import citrus.core.CitrusObject; + import citrus.core.IState; + import citrus.core.starling.StarlingCitrusEngine; + import citrus.physics.APhysicsEngine; + import citrus.physics.IDebugView; + import citrus.system.components.ViewComponent; + import citrus.view.ACitrusCamera; + import citrus.view.ACitrusView; + import citrus.view.ICitrusArt; + import citrus.view.ISpriteView; + + import dragonBones.Armature; + import dragonBones.animation.WorldClock; + + import starling.core.Starling; + import starling.display.DisplayObject; + import starling.display.Image; + import starling.display.MovieClip; + import starling.display.Quad; + import starling.display.Sprite; + import starling.extensions.particles.PDParticleSystem; + import starling.textures.Texture; + import starling.utils.deg2rad; + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Loader; + import flash.events.Event; + import flash.events.IOErrorEvent; + import flash.geom.Matrix; + import flash.geom.Rectangle; + import flash.net.URLLoader; + import flash.net.URLLoaderDataFormat; + import flash.net.URLRequest; + import flash.system.ApplicationDomain; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + import flash.utils.getDefinitionByName; + + /** + * This is the class that all art objects use for the StarlingView state view. If you are using the StarlingView (as opposed to the blitting view, for instance), + * then all your graphics will be an instance of this class. + *
      There are 2 ways to manage MovieClip/animations : + *
    • specify a "object.swf" in the view property of your object's creation.
    • + *
    • add an AnimationSequence to your view property of your object's creation, see the AnimationSequence for more information about it.
    • + * The AnimationSequence is more optimized than the .swf (which creates textures "on the fly" thanks to the DynamicAtlas class). You can also use the awesome + * DragonBones 2D skeleton animation solution.
    + * + *
      This class does the following things: + * + *
    • Creates the appropriate graphic depending on your CitrusObject's view property (loader, sprite, or bitmap), and loads it if it is a non-embedded graphic.
    • + *
    • Aligns the graphic with the appropriate registration (topLeft or center).
    • + *
    • Calls the MovieClip's appropriate frame label based on the CitrusObject's animation property.
    • + *
    • Updates the graphic's properties to be in-synch with the CitrusObject's properties once per frame.
    + * + *

    These objects will be created by the Citrus Engine's StarlingView, so you should never make them yourself. When you use view.getArt() to gain access to your game's graphics + * (for adding click events, for instance), you will get an instance of this object. It extends Sprite, so you can do all the expected stuff with it, + * such as add click listeners, change the alpha, etc.

    + **/ + public class StarlingArt extends Sprite implements ICitrusArt { + + // The reference to your art via the view. + private var _content:starling.display.DisplayObject; + + /** + * For objects that are loaded at runtime, this is the object that load them. Then, once they are loaded, the content + * property is assigned to loader.content. + */ + public var loader:Loader; + + // properties : + + private static var _loopAnimation:Dictionary = new Dictionary(); + + private static var _m:Matrix = new Matrix(); + + private var _ce:StarlingCitrusEngine; + + private var _citrusObject:ISpriteView; + private var _physicsComponent:*; + private var _registration:String; + private var _view:*; + private var _animation:String; + public var group:uint; + + private var _texture:Texture; + + private var _viewHasChanged:Boolean = false; // when the view changed, the animation wasn't updated if it was the same name. This var fix that. + private var _updateArtEnabled:Boolean = true; + + public function StarlingArt(object:ISpriteView = null) { + + _ce = CitrusEngine.getInstance() as StarlingCitrusEngine; + + if (object) + initialize(object); + } + + public function initialize(object:ISpriteView):void { + + _citrusObject = object; + + _ce.onPlayingChange.add(_pauseAnimation); + + var ceState:IState = _ce.state; + + if (_citrusObject is ViewComponent && ceState.getFirstObjectByType(APhysicsEngine) as APhysicsEngine) + _physicsComponent = (_citrusObject as ViewComponent).entity.lookupComponentByName("physics"); + + this.name = (_citrusObject as CitrusObject).name; + + if (_loopAnimation["walk"] != true) { + _loopAnimation["walk"] = true; + } + } + + /** + * The content property is the actual display object that your game object is using. For graphics that are loaded at runtime + * (not embedded), the content property will not be available immediately. You can listen to the COMPLETE event on the loader + * (or rather, the loader's contentLoaderInfo) if you need to know exactly when the graphic will be loaded. + */ + public function get content():starling.display.DisplayObject { + return _content; + } + + public function destroy():void { + + if (_viewHasChanged) + removeChild(_content); + else + _ce.onPlayingChange.remove(_pauseAnimation); + + if (_content is starling.display.MovieClip) { + + _ce.juggler.remove(_content as starling.display.MovieClip); + _content.dispose(); + + } else if (_content is AnimationSequence) { + + (_content as AnimationSequence).destroy(); + _content.dispose(); + + } else if (_content is Image) { + + if (_texture) + _texture.dispose(); + + _content.dispose(); + + } else if (_content is PDParticleSystem) { + + _ce.juggler.remove(_content as PDParticleSystem); + (_content as PDParticleSystem).stop(); + _content.dispose(); + + } else if (_content is StarlingTileSystem) { + (_content as StarlingTileSystem).destroy(); + _content.dispose(); + + } else if (_view is Armature) { + WorldClock.clock.remove(_view); + (_view as Armature).dispose(); + _content.dispose(); + + } else if (_content is starling.display.DisplayObject) { + + _content.dispose(); + } + + _viewHasChanged = false; + } + + /** + * Add a loop animation to the Dictionnary. + * @param tab an array with all the loop animation names. + */ + static public function setLoopAnimations(tab:Array):void { + + for each (var animation:String in tab) { + _loopAnimation[animation] = true; + } + } + + /** + * Determines animations playing in loop. You can add one in your state class: StarlingArt.setLoopAnimations(["walk", "climb"]); + */ + static public function get loopAnimation():Dictionary { + return _loopAnimation; + } + + public function moveRegistrationPoint(registrationPoint:String):void { + + if (registrationPoint == "topLeft") { + _content.x = 0; + _content.y = 0; + } else if (registrationPoint == "center") { + _content.x = -_content.width / 2; + _content.y = -_content.height / 2; + } + } + + /** + * align suggestion wip + */ + private static var rectBounds:Rectangle = new Rectangle(); + public function align(mulX:Number = .5, mulY:Number = .5,offX:Number = 0,offY:Number = 0):void + { + if(_content.parent == this) + _content.getBounds(this, rectBounds); + else + rectBounds.setTo(0, 0, 0, 0); + + _content.x = -rectBounds.x - rectBounds.width*mulX + offX; + _content.y = -rectBounds.y - rectBounds.height*mulY + offY; + } + + public function get registration():String { + return _registration; + } + + public function set registration(value:String):void { + + if (_registration == value || !_content) + return; + + _registration = value; + + moveRegistrationPoint(_registration); + } + + public function get view():* { + return _view; + } + + public function set view(value:*):void { + + if (_view == value) + return; + + if (_content && _content.parent) { + _viewHasChanged = true; + _citrusObject.handleArtChanged(this as ICitrusArt); + destroy(); + _content = null; + } + + _view = value; + + if (_view) { + + var tmpObj:*; + var contentChanged:Boolean = true; + + if (_view is String) { + // view property is a path to an image? + var classString:String = _view; + var suffix:String = classString.substring(classString.length - 4).toLowerCase(); + var url:URLRequest = new URLRequest(classString); + + if (suffix == ".swf" || suffix == ".png" || suffix == ".gif" || suffix == ".jpg") { + + loader = new Loader(); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleContentLoaded); + loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, handleContentIOError); + loader.load(url, new LoaderContext(false, ApplicationDomain.currentDomain, null)); + return; + } + else if (suffix == ".atf") { + + var urlLoader:URLLoader = new URLLoader(); + urlLoader.dataFormat = URLLoaderDataFormat.BINARY; + urlLoader.addEventListener(Event.COMPLETE, handleBinaryContentLoaded); + urlLoader.load(url); + return; + } + // view property is a fully qualified class name in string form. + else { + + try + { + var artClass:Class = getDefinitionByName(classString) as Class; + }catch (e:Error) + { + throw new Error("[StarlingArt] could not find class definition for \"" + String(classString) + "\". \n Make sure that you compile it with the project or that its the right classpath."); + } + + tmpObj = new artClass(); + + if (tmpObj is flash.display.MovieClip) { + _content = AnimationSequence.fromMovieClip(tmpObj, _animation, 30); + } + else if (tmpObj is flash.display.Bitmap) { + _content = new Image(_texture = Texture.fromBitmap(tmpObj, false, false, _ce.scaleFactor)); + } + else if (tmpObj is BitmapData) { + _content = new Image(_texture = Texture.fromBitmapData(tmpObj, false, false, _ce.scaleFactor)); + } + else if(tmpObj is starling.display.DisplayObject) { + _content = tmpObj; + } + else + throw new Error("[StarlingArt] class" + String(classString) + " does not define a DisplayObject."); + + } + + } else if (_view is Class) { + + tmpObj = new _view(); + if (tmpObj is flash.display.MovieClip) { + _content = AnimationSequence.fromMovieClip(tmpObj, _animation, 30); + } + else if (tmpObj is flash.display.Bitmap) { + _content = new Image(_texture = Texture.fromBitmap(tmpObj, false, false, _ce.scaleFactor)); + } + else if (tmpObj is BitmapData) { + _content = new Image(_texture = Texture.fromBitmapData(tmpObj, false, false, _ce.scaleFactor)); + } + else if(tmpObj is starling.display.DisplayObject) { + _content = tmpObj; + } + + } else if (_view is flash.display.MovieClip) { + _content = AnimationSequence.fromMovieClip(_view, _animation, 30); + + } else if (_view is starling.display.DisplayObject) { + + _content = _view; + + if (_view is starling.display.MovieClip) + _ce.juggler.add(_content as starling.display.MovieClip); + else if (_view is PDParticleSystem) + _ce.juggler.add(_content as PDParticleSystem); + + } else if (_view is Texture) { + _content = new Image(_view); + + } else if (_view is Bitmap) { + // TODO : cut bitmap if size > 2048 * 2048, use StarlingTileSystem? + _content = new Image(_texture = Texture.fromBitmap(_view, false, false, _ce.scaleFactor)); + + } else if (_view is Armature) { + _content = (_view as Armature).display as Sprite; + WorldClock.clock.add(_view); + + } else if (_view is uint) { + + // TODO : manage radius -> circle + _content = new Quad(_citrusObject.width, _citrusObject.height, _view); + } else + contentChanged = false; + + if(_content == null || contentChanged == false) + throw new Error("StarlingArt doesn't know how to create a graphic object from the provided CitrusObject " + citrusObject); + else + { + moveRegistrationPoint(_citrusObject.registration); + + if (_content.hasOwnProperty("initialize")) + _content["initialize"](_citrusObject); + addChild(_content); + + _citrusObject.handleArtReady(this as ICitrusArt); + } + + } + } + + public function get animation():String { + return _animation; + } + + public function set animation(value:String):void { + + if (_animation == value && !_viewHasChanged) + return; + + _animation = value; + + if (_animation != null && _animation != "") { + + var animLoop:Boolean = _loopAnimation[_animation]; + + if (_content is AnimationSequence) + (_content as AnimationSequence).changeAnimation(_animation, animLoop); + else if (_view is Armature) + (_view as Armature).animation.gotoAndPlay(value, -1, -1, animLoop ? 0 : 1); + } + + _viewHasChanged = false; + } + + public function get citrusObject():ISpriteView { + return _citrusObject; + } + + public function update(stateView:ACitrusView):void { + if (_citrusObject.inverted) { + + if (scaleX > 0) + scaleX = -scaleX; + + } else { + + if (scaleX < 0) + scaleX = -scaleX; + } + + if (_content is StarlingPhysicsDebugView) { + + var physicsDebugArt:IDebugView = (_content as StarlingPhysicsDebugView).debugView as IDebugView; + /** + * INFO : + * can be replaced with (stateView as StarlingView).viewRoot as Sprite).getTransformationMatrix(Starling.current.stage) + * or using transform.concatenatedMatrix in SpriteArt . This would solve any issues with moved root sprite, state sprite, + * or any further parents added by the user that we don't know of. + */ + _m.copyFrom(stateView.camera.transformMatrix); + _m.concat(_ce.transformMatrix); + physicsDebugArt.transformMatrix = _m; + physicsDebugArt.visibility = _citrusObject.visible; + + (_content as StarlingPhysicsDebugView).update(); + + } else if (_physicsComponent) { + + x = _physicsComponent.x + ( (stateView.camera.camProxy.x - _physicsComponent.x) * (1 - _citrusObject.parallaxX)) + _citrusObject.offsetX * scaleX; + y = _physicsComponent.y + ( (stateView.camera.camProxy.y - _physicsComponent.y) * (1 - _citrusObject.parallaxY)) + _citrusObject.offsetY; + rotation = deg2rad(_physicsComponent.rotation); + + } else { + if (stateView.camera.parallaxMode == ACitrusCamera.PARALLAX_MODE_DEPTH) + { + x = _citrusObject.x + ( (stateView.camera.camProxy.x - _citrusObject.x) * (1 - _citrusObject.parallaxX)) + _citrusObject.offsetX * scaleX; + y = _citrusObject.y + ( (stateView.camera.camProxy.y - _citrusObject.y) * (1 - _citrusObject.parallaxY)) + _citrusObject.offsetY; + } + else + { + x = _citrusObject.x + ( (stateView.camera.camProxy.x + stateView.camera.camProxy.offset.x) * (1 - _citrusObject.parallaxX)) + _citrusObject.offsetX * scaleX; + y = _citrusObject.y + ( (stateView.camera.camProxy.y + stateView.camera.camProxy.offset.y) * (1 - _citrusObject.parallaxY)) + _citrusObject.offsetY; + } + rotation = deg2rad(_citrusObject.rotation); + } + + visible = _citrusObject.visible; + touchable = _citrusObject.touchable; + registration = _citrusObject.registration; + view = _citrusObject.view; + animation = _citrusObject.animation; + group = _citrusObject.group; + } + + /** + * play/pause animation when "playing" changes. The citrus juggler is pausable so no need to add/remove anything to it here. + */ + private function _pauseAnimation(value:Boolean):void { + if (_view is Armature) + value ? (_view as Armature).animation.play() : (_view as Armature).animation.stop(); + } + + private function handleContentLoaded(evt:Event):void { + + loader = null; + + (evt.target.loader as Loader).removeEventListener(Event.COMPLETE, handleContentLoaded); + (evt.target.loader as Loader).removeEventListener(IOErrorEvent.IO_ERROR, handleContentIOError); + + if (!(evt.target.loader.content is flash.display.MovieClip || + evt.target.loader.content is Bitmap)) + { + throw new Error("StarlingArt: Loaded content for "+(_citrusObject as CitrusObject).name+" can only be a MovieClip or a Bitmap"); + } + + if (_content && _content.parent) + { + _viewHasChanged = true; + destroy(); + } + + if (evt.target.loader.content is flash.display.MovieClip) + _content = AnimationSequence.fromMovieClip(evt.target.loader.content, _animation, 30); + else if (evt.target.loader.content is Bitmap) + _content = new Image(_texture = Texture.fromBitmap(evt.target.loader.content, false, false, _ce.scaleFactor)); + + moveRegistrationPoint(_citrusObject.registration); + addChild(_content); + _citrusObject.handleArtReady(this as ICitrusArt); + } + + /** + * Handles loading of the atf assets. + */ + private function handleBinaryContentLoaded(evt:Event):void { + + loader = null; + + evt.target.removeEventListener(Event.COMPLETE, handleBinaryContentLoaded); + + _texture = Texture.fromAtfData(evt.target.data as ByteArray, _ce.scaleFactor, false); + _content = new Image(_texture); + + moveRegistrationPoint(_citrusObject.registration); + addChild(_content); + _citrusObject.handleArtReady(this as ICitrusArt); + } + + private function handleContentIOError(evt:IOErrorEvent):void { + loader = null; + throw new Error(evt.text); + } + + /** + * Set it to false if you want to prevent the art to be updated. Be careful its properties (x, y, ...) won't be able to change! + */ + public function get updateArtEnabled():Boolean { + return _updateArtEnabled; + } + + /** + * Set it to false also made the Sprite flattened! + */ + public function set updateArtEnabled(value:Boolean):void { + _updateArtEnabled = value; + _updateArtEnabled ? unflatten() : flatten(); + } + + } +} diff --git a/src/citrus/view/starlingview/StarlingCamera.as b/src/citrus/view/starlingview/StarlingCamera.as new file mode 100644 index 00000000..3ebe1457 --- /dev/null +++ b/src/citrus/view/starlingview/StarlingCamera.as @@ -0,0 +1,532 @@ +package citrus.view.starlingview { + + import citrus.core.starling.StarlingCitrusEngine; + import citrus.math.MathUtils; + import citrus.view.ACitrusCamera; + + import starling.display.Sprite; + + import flash.display.Sprite; + import flash.geom.Point; + import flash.geom.Rectangle; + + + + /** + * The Camera for the StarlingView. + */ + public class StarlingCamera extends ACitrusCamera + { + + public function StarlingCamera(viewRoot:starling.display.Sprite) + { + super(viewRoot); + } + + /** + * @inheritDoc + */ + override protected function initialize():void { + super.initialize();// setup camera lens normally + + cameraLensWidth = (_ce as StarlingCitrusEngine).starling.stage.stageWidth; + cameraLensHeight = (_ce as StarlingCitrusEngine).starling.stage.stageHeight; + + _aabbData = MathUtils.createAABBData(0, 0, cameraLensWidth / _camProxy.scale, cameraLensHeight / _camProxy.scale, _camProxy.rotation, _aabbData); + _m = (_viewRoot as starling.display.Sprite).transformationMatrix; + } + + /** + * @inheritDoc + */ + override protected function onResize(w:Number, h:Number):void + { + cameraLensWidth = (_ce as StarlingCitrusEngine).starling.stage.stageWidth; + cameraLensHeight = (_ce as StarlingCitrusEngine).starling.stage.stageHeight; + } + + /** + * multiplies the targeted zoom value by factor. + * @param factor + */ + override public function zoom(factor:Number):void + { + if (_allowZoom) + _zoom *= factor; + else + throw(new Error(this+" is not allowed to zoom. please set allowZoom to true.")); + } + + /** + * @inheritDoc + */ + override public function zoomFit(width:Number,height:Number,storeInBaseZoom:Boolean = false):Number + { + if (_allowZoom) + { + var ratio:Number; + if (cameraLensHeight / cameraLensWidth > height / width) + ratio = cameraLensWidth / width; + else + ratio = cameraLensHeight / height; + + if (storeInBaseZoom) + { + baseZoom = ratio; + _zoom = 1; + return ratio; + } + else + return _zoom = ratio; + } + else + throw(new Error(this+" is not allowed to zoom. please set allowZoom to true.")); + } + + /** + * rotates the camera by the angle. + * adds angle to targeted rotation value. + * @param angle in radians. + */ + override public function rotate(angle:Number):void + { + if (_allowRotation) + _rotation += angle; + else + throw(new Error(this+" is not allowed to rotate. please set allowRotation to true.")); + } + + /** + * sets the targeted rotation value to angle. + * @param angle in radians. + */ + override public function setRotation(angle:Number):void + { + if (_allowRotation) + _rotation = angle; + else + throw(new Error(this+" is not allowed to rotate. please set allowRotation to true.")); + } + + /** + * sets the targeted zoom value to factor. + * @param factor + */ + override public function setZoom(factor:Number):void + { + if (_allowZoom) + _zoom = factor; + else + throw(new Error(this+" is not allowed to zoom. please set allowZoom to true.")); + } + + /** + * @inheritDoc + */ + override public function getZoom():Number + { + return _zoom; + } + + /** + * @inheritDoc + */ + override public function getRotation():Number + { + return _rotation; + } + + /** + * Recreates the AABB of the camera. + * will use Math.Utils.createAABBData when allowRotation = true. + */ + public function resetAABBData():void + { + if (!_allowZoom && !_allowRotation) + { + _aabbData.offsetX = _aabbData.offsetY = 0; + _aabbData.rect.setTo(_ghostTarget.x, _ghostTarget.y, cameraLensWidth, cameraLensHeight); + return; + } + + if (_allowZoom && !_allowRotation) + { + _aabbData.offsetX = _aabbData.offsetY = 0; + _aabbData.rect.setTo(_ghostTarget.x, _ghostTarget.y, cameraLensWidth / _camProxy.scale, cameraLensHeight / _camProxy.scale); + return; + } + + if (_allowRotation && _allowZoom) + { + _aabbData = MathUtils.createAABBData(_ghostTarget.x , _ghostTarget.y, cameraLensWidth / _camProxy.scale, cameraLensHeight / _camProxy.scale, - _camProxy.rotation, _aabbData); + return; + } + + if (!_allowZoom && _allowRotation) + { + _aabbData = MathUtils.createAABBData(_ghostTarget.x , _ghostTarget.y, cameraLensWidth, cameraLensHeight, - _camProxy.rotation, _aabbData); + return; + } + + } + + /** + * @inheritDoc + */ + override public function update():void + { + super.update(); + + offset.setTo(cameraLensWidth * center.x, cameraLensHeight * center.y); + + if (_target && followTarget) + { + if (_target.x <= camPos.x - (deadZone.width * .5) / _camProxy.scale || _target.x >= camPos.x + (deadZone.width * .5) / _camProxy.scale ) + _targetPos.x = _target.x; + + if (_target.y <= camPos.y - (deadZone.height * .5) / _camProxy.scale || _target.y >= camPos.y + (deadZone.height * .5) / _camProxy.scale) + _targetPos.y = _target.y; + + _ghostTarget.x += (_targetPos.x - _ghostTarget.x) * easing.x; + _ghostTarget.y += (_targetPos.y - _ghostTarget.y) * easing.y; + } + else if (_manualPosition) + { + _ghostTarget.x = _manualPosition.x; + _ghostTarget.y = _manualPosition.y; + } + + if (_allowRotation) + _camProxy.rotation += (_rotation - _camProxy.rotation) * rotationEasing; + + resetAABBData(); + + if (_allowZoom) + { + + _camProxy.scale += (mzoom - _camProxy.scale) * zoomEasing; + + if (bounds && (boundsMode == BOUNDS_MODE_AABB || boundsMode == BOUNDS_MODE_ADVANCED) ) + { + var lwratio:Number = (_aabbData.rect.width*_camProxy.scale ) / bounds.width; + var lhratio:Number = (_aabbData.rect.height*_camProxy.scale ) / bounds.height; + + if (_aabbData.rect.width >= bounds.width) + _camProxy.scale = mzoom = lwratio; + else if (_aabbData.rect.height >= bounds.height) + _camProxy.scale = mzoom = lhratio; + } + + } + + _camProxy.x = ghostTarget.x; + _camProxy.y = ghostTarget.y; + + MathUtils.rotatePoint(offset.x/_camProxy.scale, offset.y/_camProxy.scale, _camProxy.rotation, _b.rotoffset); + + if ( bounds ) + { + + if (boundsMode == BOUNDS_MODE_AABB) + { + + _b.w2 = (_aabbData.rect.width - _b.rotoffset.x) + _aabbData.offsetX; + _b.h2 = (_aabbData.rect.height - _b.rotoffset.y) + _aabbData.offsetY; + + _b.bl = bounds.left + ( MathUtils.abs(_aabbData.offsetX) + _b.rotoffset.x ); + _b.bt = bounds.top + ( MathUtils.abs(_aabbData.offsetY) + _b.rotoffset.y ); + _b.br = bounds.right - ( (_aabbData.offsetX+_aabbData.rect.width) - _b.rotoffset.x ); + _b.bb = bounds.bottom - ( (_aabbData.offsetY+_aabbData.rect.height) - _b.rotoffset.y); + + if (_camProxy.x < _b.bl) + _camProxy.x = _b.bl; + if (_camProxy.x > _b.br) + _camProxy.x = _b.br; + if (_camProxy.y < _b.bt) + _camProxy.y = _b.bt; + if (_camProxy.y > _b.bb) + _camProxy.y = _b.bb; + + }else if (boundsMode == BOUNDS_MODE_OFFSET) + { + if (_camProxy.x < bounds.left) + _camProxy.x = bounds.left; + if (_camProxy.x > bounds.right) + _camProxy.x = bounds.right; + if (_camProxy.y < bounds.top) + _camProxy.y = bounds.top; + if (_camProxy.y > bounds.bottom) + _camProxy.y = bounds.bottom; + + }else if (boundsMode == BOUNDS_MODE_ADVANCED) + { + /** + * Find the furthest camera corner from the offset point, and use the distance from offset to that corner + * as the radius of the circle that will be restricted within the bounds. + */ + + if (offset.x <= cameraLensWidth * 0.5) //left + { + if (offset.y <= cameraLensHeight * 0.5) //top + _b.diag2 = MathUtils.DistanceBetweenTwoPoints(offset.x, cameraLensWidth, offset.y, cameraLensHeight); + else + _b.diag2 = MathUtils.DistanceBetweenTwoPoints(offset.x, cameraLensWidth, offset.y, 0); + }else + { + if (offset.y <= cameraLensHeight * 0.5) //top + _b.diag2 = MathUtils.DistanceBetweenTwoPoints(offset.x, 0, offset.y, cameraLensHeight); + else + _b.diag2 = offset.length; + } + + _b.diag2 /= _camProxy.scale; + + if (_camProxy.x < bounds.left + _b.diag2) + _camProxy.x = bounds.left + _b.diag2; + if (_camProxy.x > bounds.right - _b.diag2) + _camProxy.x = bounds.right - _b.diag2; + if (_camProxy.y < bounds.top + _b.diag2) + _camProxy.y = bounds.top + _b.diag2; + if (_camProxy.y > bounds.bottom - _b.diag2) + _camProxy.y = bounds.bottom - _b.diag2; + } + } + + if (parallaxMode == PARALLAX_MODE_TOPLEFT) + { + _m.identity(); + _m.rotate(_camProxy.rotation); + _m.scale(1/_camProxy.scale, 1/_camProxy.scale); + _camProxy.offset = _m.transformPoint(offset); + _camProxy.offset.x *= -1; + _camProxy.offset.y *= -1; + } + + _aabbData.rect.x = _camProxy.x + _aabbData.offsetX - _b.rotoffset.x; + _aabbData.rect.y = _camProxy.y + _aabbData.offsetY - _b.rotoffset.y; + + //reset matrix + _m.identity(); + //fake pivot + _m.translate( -_camProxy.x, -_camProxy.y); + //rotation + _m.rotate(_camProxy.rotation); + //zoom + _m.scale(_camProxy.scale, _camProxy.scale); + //offset + _m.translate(offset.x, offset.y); + + pointFromLocal(offset.x, offset.y, _camPos); + + (_viewRoot as starling.display.Sprite).transformationMatrix = _m; + } + + /** + * @param sprite a flash display sprite to render to. + * @deprecated this is now obsolete and doesn't reflect exactly how the camera works as the system changed. + */ + public function renderDebug(sprite:flash.display.Sprite):void + { + + var xo:Number, yo:Number, w:Number, h:Number; + + //create AABB of camera + var AABB:Object = MathUtils.createAABBData( + + _ghostTarget.x , + _ghostTarget.y , + + cameraLensWidth / _camProxy.scale, + cameraLensHeight / _camProxy.scale, + - _camProxy.rotation); + + sprite.graphics.clear(); + + if (bounds) + { + //draw bounds + sprite.graphics.lineStyle(1, 0xFF0000); + sprite.graphics.drawRect( + bounds.left, + bounds.top, + bounds.width, + bounds.height); + } + + //draw targets + sprite.graphics.lineStyle(20, 0xFF0000); + if (_target) + sprite.graphics.drawCircle(_target.x, _target.y, 10); + sprite.graphics.drawCircle(_ghostTarget.x, _ghostTarget.y, 10); + + //rotate and scale offset. + var rotScaledOffset:Point = MathUtils.rotatePoint( + offset.x / _camProxy.scale, offset.y / _camProxy.scale, + _camProxy.rotation); + + //offset aabb rect according to rotated and scaled camera offset + AABB.rect.x -= rotScaledOffset.x; + AABB.rect.y -= rotScaledOffset.y; + + //draw aabb + sprite.graphics.lineStyle(1, 0xFFFF00); + sprite.graphics.drawRect(AABB.rect.x, AABB.rect.y, AABB.rect.width, AABB.rect.height); + + var c:Number = Math.cos(_camProxy.rotation); + var s:Number = Math.sin(_camProxy.rotation); + + //draw rotated camera rect + + xo = AABB.rect.x - AABB.offsetX; + yo = AABB.rect.y - AABB.offsetY; + + w = cameraLensWidth / _camProxy.scale; + h = cameraLensHeight / _camProxy.scale; + + sprite.graphics.lineStyle(1, 0x00F0FF); + sprite.graphics.beginFill(0x000000, 0.2); + sprite.graphics.moveTo(xo, + yo); + sprite.graphics.lineTo( + xo + (w) * c + (0) * s , + yo + -(w) * s + (0) * c ); + sprite.graphics.lineTo( + xo + (w) * c + (h) * s , + yo + -(w) * s + (h) * c ); + sprite.graphics.lineTo( + xo + (0) * c + (h) * s , + yo + -(0) * s + (h) * c ); + sprite.graphics.lineTo(xo , + yo); + sprite.graphics.endFill(); + + if (bounds && !bounds.containsRect(AABB.rect)) + { + //aabb is out of bounds, draw where it should be if constrained + + var newAABBPos:Point = new Point(AABB.rect.x,AABB.rect.y); + + //x + if (AABB.rect.left <= bounds.left) + newAABBPos.x = bounds.left; + else if (AABB.rect.right >= bounds.right) + newAABBPos.x = bounds.right - AABB.rect.width; + + //y + if (AABB.rect.top <= bounds.top) + newAABBPos.y = bounds.top; + else if (AABB.rect.bottom >= bounds.bottom) + newAABBPos.y = bounds.bottom - AABB.rect.height; + + sprite.graphics.lineStyle(1, 0xFFFFFF , 0.5); + sprite.graphics.drawRect(newAABBPos.x, newAABBPos.y, AABB.rect.width, AABB.rect.height); + + //then using the new aabb position... draw the camera. + + xo = newAABBPos.x - AABB.offsetX; + yo = newAABBPos.y - AABB.offsetY; + + w = cameraLensWidth / _camProxy.scale; + h = cameraLensHeight / _camProxy.scale; + + sprite.graphics.lineStyle(1, 0xFFFFFF, 0.5); + sprite.graphics.beginFill(0xFFFFFF, 0.1); + sprite.graphics.moveTo(xo, + yo); + sprite.graphics.lineTo( + xo + (w) * c + (0) * s , + yo + -(w) * s + (0) * c ); + sprite.graphics.lineTo( + xo + (w) * c + (h) * s , + yo + -(w) * s + (h) * c ); + sprite.graphics.lineTo( + xo + (0) * c + (h) * s , + yo + -(0) * s + (h) * c ); + sprite.graphics.lineTo(xo , + yo); + sprite.graphics.endFill(); + + //and so the new position of the camera : + + var newGTPos:Point = new Point(newAABBPos.x, newAABBPos.y); + + sprite.graphics.lineStyle(20, 0xFFFFFF); + sprite.graphics.drawCircle(newGTPos.x, newGTPos.y, 10); + + newGTPos.x -= AABB.offsetX; + newGTPos.y -= AABB.offsetY; + + sprite.graphics.drawCircle(newGTPos.x, newGTPos.y, 10); + + //and we already have the rotated and scaled offset so lets add it. + + newGTPos.x += rotScaledOffset.x; + newGTPos.y += rotScaledOffset.y; + + sprite.graphics.drawCircle(newGTPos.x, newGTPos.y, 10); + + } + + } + + /** + * equivalent of globalToLocal. + */ + public function pointFromLocal(x:Number,y:Number,resultPoint:Point = null):Point + { + _p.setTo(x, y); + return (_viewRoot as starling.display.Sprite).globalToLocal(_p,resultPoint); + } + + /** + * equivalent of localToGlobal + */ + public function pointToLocal(p:Point):Point + { + return (_viewRoot as starling.display.Sprite).localToGlobal(p); + } + + /** + * @inheritDoc + */ + override public function get allowZoom():Boolean + { + return _allowZoom; + } + + /** + * @inheritDoc + */ + override public function get allowRotation():Boolean + { + return _allowRotation; + } + + /** + * @inheritDoc + */ + override public function set allowZoom(value:Boolean):void + { + if (!value) + { + _zoom = 1; + _camProxy.scale = 1; + } + _allowZoom = value; + } + + /** + * @inheritDoc + */ + override public function set allowRotation(value:Boolean):void + { + if (!value) + { + _rotation = 0; + _camProxy.rotation = 0; + } + _allowRotation = value; + } + + } +} diff --git a/src/citrus/view/starlingview/StarlingPhysicsDebugView.as b/src/citrus/view/starlingview/StarlingPhysicsDebugView.as new file mode 100644 index 00000000..f6fe4c50 --- /dev/null +++ b/src/citrus/view/starlingview/StarlingPhysicsDebugView.as @@ -0,0 +1,54 @@ +package citrus.view.starlingview { + + import citrus.core.CitrusEngine; + import citrus.physics.APhysicsEngine; + import citrus.physics.IDebugView; + import flash.display.Sprite; + import starling.core.Starling; + import starling.display.Sprite; + import starling.events.Event; + + + + /** + * A wrapper for Starling to display the debug view of the different physics engine. + */ + public class StarlingPhysicsDebugView extends starling.display.Sprite { + + private var _physicsEngine:APhysicsEngine; + private var _debugView:IDebugView; + + public function StarlingPhysicsDebugView() { + + _physicsEngine = CitrusEngine.getInstance().state.getFirstObjectByType(APhysicsEngine) as APhysicsEngine; + _debugView = new _physicsEngine.realDebugView(); + addEventListener(Event.ADDED_TO_STAGE, _addedToStage); + } + + private function _addedToStage(event:Event):void { + removeEventListener(Event.ADDED_TO_STAGE, _addedToStage); + _debugView.initialize(); + } + + public function update():void { + _debugView.update(); + } + + public function debugMode(flags:uint):void { + _debugView.debugMode(flags); + } + + public function get debugView():IDebugView { + return _debugView; + } + + override public function dispose():void + { + _debugView.destroy(); + _physicsEngine = null; + _debugView = null; + super.dispose(); + } + + } +} diff --git a/src/citrus/view/starlingview/StarlingSpriteDebugArt.as b/src/citrus/view/starlingview/StarlingSpriteDebugArt.as new file mode 100644 index 00000000..9b093732 --- /dev/null +++ b/src/citrus/view/starlingview/StarlingSpriteDebugArt.as @@ -0,0 +1 @@ +package citrus.view.starlingview { import citrus.core.CitrusObject; import citrus.objects.CitrusSprite; import starling.display.DisplayObjectContainer; import starling.display.Quad; /** * This class is created by the StarlingView if a CitrusSprite has no view mentionned. It is made for a quick debugging object's view. */ public class StarlingSpriteDebugArt extends DisplayObjectContainer { public function StarlingSpriteDebugArt() { super(); } public function initialize(object:CitrusObject):void { var citrusSprite:CitrusSprite = object as CitrusSprite; if (citrusSprite) { var quad:Quad = new Quad(citrusSprite.width, citrusSprite.height, 0x888888); addChild(quad); } } } } \ No newline at end of file diff --git a/src/citrus/view/starlingview/StarlingView.as b/src/citrus/view/starlingview/StarlingView.as new file mode 100644 index 00000000..c40de88d --- /dev/null +++ b/src/citrus/view/starlingview/StarlingView.as @@ -0,0 +1,111 @@ +package citrus.view.starlingview { + + import citrus.physics.APhysicsEngine; + import citrus.view.ACitrusView; + import citrus.view.ISpriteView; + import citrus.view.spriteview.SpriteDebugArt; + + import dragonBones.animation.WorldClock; + + import starling.display.Sprite; + + import flash.display.MovieClip; + + /** + * StarlingView is based on Adobe Stage3D and the Starling framework to render graphics. + * It creates and manages graphics like the traditional Flash display list (but on the GPU!!) thanks to Starling : + * (addChild(), removeChild()) using Starling DisplayObjects (MovieClip, Image, Sprite, Quad etc). + */ + public class StarlingView extends ACitrusView { + + private var _viewRoot:Sprite; + + public function StarlingView(root:Sprite) { + + super(root, ISpriteView); + + root.alpha = 0.999; // Starling's simple trick to avoid the state changes. + + _viewRoot = new Sprite(); + root.addChild(_viewRoot); + + camera = new StarlingCamera(_viewRoot); + } + + public function get viewRoot():Sprite { + return _viewRoot; + } + + override public function destroy():void { + + _viewRoot.removeChildren(); //any remaining children. + _viewRoot.removeFromParent(true); + super.destroy(); + _viewRoot = null; + } + + override public function update(timeDelta:Number):void { + + super.update(timeDelta); + + // Update art positions + for each (var sprite:StarlingArt in _viewObjects) { + if (sprite.group != sprite.citrusObject.group) + updateGroupForSprite(sprite); + + if (sprite.updateArtEnabled) + sprite.update(this); + } + + WorldClock.clock.advanceTime(timeDelta); + + if (camera.enabled) + camera.update(); + } + + override protected function createArt(citrusObject:Object):Object { + + var viewObject:ISpriteView = citrusObject as ISpriteView; + + if (citrusObject is APhysicsEngine) + citrusObject.view = StarlingPhysicsDebugView; + + if (citrusObject.view == SpriteDebugArt) + citrusObject.view = StarlingSpriteDebugArt; + + if (citrusObject.view == flash.display.MovieClip) + citrusObject.view = starling.display.Sprite; + + var art:StarlingArt = new StarlingArt(viewObject); + + // Perform an initial update + art.update(this); + + updateGroupForSprite(art); + + return art; + } + + override protected function destroyArt(citrusObject:Object):void { + + var starlingArt:StarlingArt = _viewObjects[citrusObject]; + starlingArt.destroy(); + starlingArt.parent.removeChild(starlingArt); + } + + private function updateGroupForSprite(sprite:StarlingArt):void { + + if (sprite.citrusObject.group > _viewRoot.numChildren + 100) + trace("the group property value of " + sprite.citrusObject + ":" + sprite.citrusObject.group + " is higher than +100 to the current max group value (" + _viewRoot.numChildren + ") and may perform a crash"); + + // Create the container sprite (group) if it has not been created yet. + while (sprite.citrusObject.group >= _viewRoot.numChildren) + _viewRoot.addChild(new Sprite()); + + // Add the sprite to the appropriate group + Sprite(_viewRoot.getChildAt(sprite.citrusObject.group)).addChild(sprite); + + // The sprite.group will be updated in the update method like all its other values. This function is called after the updateGroupForSprite method. + } + } +} diff --git a/srclib/Box2D/Collision/ClipVertex.as b/srclib/Box2D/Collision/ClipVertex.as new file mode 100644 index 00000000..4b72276c --- /dev/null +++ b/srclib/Box2D/Collision/ClipVertex.as @@ -0,0 +1,46 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + + +use namespace b2internal; + + +/** +* @private +*/ +public class ClipVertex +{ + public function ClipVertex() {} + + public function Set(other:ClipVertex):void + { + v.SetV(other.v); + id.Set(other.id); + } + + public var v:b2Vec2 = new b2Vec2(); + public var id:b2ContactID = new b2ContactID(); +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/Features.as b/srclib/Box2D/Collision/Features.as new file mode 100644 index 00000000..88ab4de3 --- /dev/null +++ b/srclib/Box2D/Collision/Features.as @@ -0,0 +1,85 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.b2internal; + +use namespace b2internal; + +/** +* We use contact ids to facilitate warm starting. +*/ +public class Features +{ + public function Features() {} + + /** + * The edge that defines the outward contact normal. + */ + public function get referenceEdge():int{ + return _referenceEdge; + } + public function set referenceEdge(value:int) : void{ + _referenceEdge = value; + _m_id._key = (_m_id._key & 0xffffff00) | (_referenceEdge & 0x000000ff); + } + b2internal var _referenceEdge:int; + + /** + * The edge most anti-parallel to the reference edge. + */ + public function get incidentEdge():int{ + return _incidentEdge; + } + public function set incidentEdge(value:int) : void{ + _incidentEdge = value; + _m_id._key = (_m_id._key & 0xffff00ff) | ((_incidentEdge << 8) & 0x0000ff00); + } + b2internal var _incidentEdge:int; + + /** + * The vertex (0 or 1) on the incident edge that was clipped. + */ + public function get incidentVertex():int{ + return _incidentVertex; + } + public function set incidentVertex(value:int) : void{ + _incidentVertex = value; + _m_id._key = (_m_id._key & 0xff00ffff) | ((_incidentVertex << 16) & 0x00ff0000); + } + b2internal var _incidentVertex:int; + + /** + * A value of 1 indicates that the reference edge is on shape2. + */ + public function get flip():int{ + return _flip; + } + public function set flip(value:int) : void{ + _flip = value; + _m_id._key = (_m_id._key & 0x00ffffff) | ((_flip << 24) & 0xff000000); + } + b2internal var _flip:int; + + + b2internal var _m_id:b2ContactID; +}; + + +} diff --git a/srclib/Box2D/Collision/IBroadPhase.as b/srclib/Box2D/Collision/IBroadPhase.as new file mode 100644 index 00000000..b00fbd2b --- /dev/null +++ b/srclib/Box2D/Collision/IBroadPhase.as @@ -0,0 +1,80 @@ +package Box2D.Collision +{ + + import Box2D.Common.Math.b2Vec2; + /** + * Interface for objects tracking overlap of many AABBs. + */ + public interface IBroadPhase + { + /** + * Create a proxy with an initial AABB. Pairs are not reported until + * UpdatePairs is called. + */ + function CreateProxy(aabb:b2AABB, userData:*):*; + + /** + * Destroy a proxy. It is up to the client to remove any pairs. + */ + function DestroyProxy(proxy:*):void; + + /** + * Call MoveProxy as many times as you like, then when you are done + * call UpdatePairs to finalized the proxy pairs (for your time step). + */ + function MoveProxy(proxy:*, aabb:b2AABB, displacement:b2Vec2):void; + + function TestOverlap(proxyA:*, proxyB:*):Boolean; + + /** + * Get user data from a proxy. Returns null if the proxy is invalid. + */ + function GetUserData(proxy:*):*; + + /** + * Get the fat AABB for a proxy. + */ + function GetFatAABB(proxy:*):b2AABB; + + /** + * Get the number of proxies. + */ + function GetProxyCount():int; + + /** + * Update the pairs. This results in pair callbacks. This can only add pairs. + */ + function UpdatePairs(callback:Function):void; + + /** + * Query an AABB for overlapping proxies. The callback class + * is called with each proxy that overlaps + * the supplied AABB, and return a Boolean indicating if + * the broaphase should proceed to the next match. + * @param callback This function should be a function matching signature + * function Callback(proxy:*):Boolean + */ + function Query(callback:Function, aabb:b2AABB):void; + + /** + * Ray-cast agains the proxies in the tree. This relies on the callback + * to perform exact ray-cast in the case where the proxy contains a shape + * The callback also performs any collision filtering + * @param callback This function should be a function matching signature + * function Callback(subInput:b2RayCastInput, proxy:*):Number + * Where the returned number is the new value for maxFraction + */ + function RayCast(callback:Function, input:b2RayCastInput):void; + + /** + * For debugging, throws in invariants have been broken + */ + function Validate():void; + + /** + * Give the broadphase a chance for structural optimizations + */ + function Rebalance(iterations:int):void; + } + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/Shapes/b2CircleShape.as b/srclib/Box2D/Collision/Shapes/b2CircleShape.as new file mode 100644 index 00000000..10381b1a --- /dev/null +++ b/srclib/Box2D/Collision/Shapes/b2CircleShape.as @@ -0,0 +1,227 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision.Shapes{ + + + + import Box2D.Collision.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; +use namespace b2internal; + + + +/** +* A circle shape. +* @see b2CircleDef +*/ +public class b2CircleShape extends b2Shape +{ + override public function Copy():b2Shape + { + var s:b2Shape = new b2CircleShape(); + s.Set(this); + return s; + } + + override public function Set(other:b2Shape):void + { + super.Set(other); + if (other is b2CircleShape) + { + var other2:b2CircleShape = other as b2CircleShape; + m_p.SetV(other2.m_p); + } + } + + /** + * @inheritDoc + */ + public override function TestPoint(transform:b2Transform, p:b2Vec2) : Boolean{ + //b2Vec2 center = transform.position + b2Mul(transform.R, m_p); + var tMat:b2Mat22 = transform.R; + var dX:Number = transform.position.x + (tMat.col1.x * m_p.x + tMat.col2.x * m_p.y); + var dY:Number = transform.position.y + (tMat.col1.y * m_p.x + tMat.col2.y * m_p.y); + //b2Vec2 d = p - center; + dX = p.x - dX; + dY = p.y - dY; + //return b2Dot(d, d) <= m_radius * m_radius; + return (dX*dX + dY*dY) <= m_radius * m_radius; + } + + /** + * @inheritDoc + */ + public override function RayCast(output:b2RayCastOutput, input:b2RayCastInput, transform:b2Transform):Boolean + { + //b2Vec2 position = transform.position + b2Mul(transform.R, m_p); + var tMat:b2Mat22 = transform.R; + var positionX:Number = transform.position.x + (tMat.col1.x * m_p.x + tMat.col2.x * m_p.y); + var positionY:Number = transform.position.y + (tMat.col1.y * m_p.x + tMat.col2.y * m_p.y); + + //b2Vec2 s = input.p1 - position; + var sX:Number = input.p1.x - positionX; + var sY:Number = input.p1.y - positionY; + //float32 b = b2Dot(s, s) - m_radius * m_radius; + var b:Number = (sX*sX + sY*sY) - m_radius * m_radius; + + /*// Does the segment start inside the circle? + if (b < 0.0) + { + output.fraction = 0; + output.hit = e_startsInsideCollide; + return; + }*/ + + // Solve quadratic equation. + //b2Vec2 r = input.p2 - input.p1; + var rX:Number = input.p2.x - input.p1.x; + var rY:Number = input.p2.y - input.p1.y; + //float32 c = b2Dot(s, r); + var c:Number = (sX*rX + sY*rY); + //float32 rr = b2Dot(r, r); + var rr:Number = (rX*rX + rY*rY); + var sigma:Number = c * c - rr * b; + + // Check for negative discriminant and short segment. + if (sigma < 0.0 || rr < Number.MIN_VALUE) + { + return false; + } + + // Find the point of intersection of the line with the circle. + var a:Number = -(c + Math.sqrt(sigma)); + + // Is the intersection point on the segment? + if (0.0 <= a && a <= input.maxFraction * rr) + { + a /= rr; + output.fraction = a; + // manual inline of: output.normal = s + a * r; + output.normal.x = sX + a * rX; + output.normal.y = sY + a * rY; + output.normal.Normalize(); + return true; + } + + return false; + } + + /** + * @inheritDoc + */ + public override function ComputeAABB(aabb:b2AABB, transform:b2Transform) : void{ + //b2Vec2 p = transform.position + b2Mul(transform.R, m_p); + var tMat:b2Mat22 = transform.R; + var pX:Number = transform.position.x + (tMat.col1.x * m_p.x + tMat.col2.x * m_p.y); + var pY:Number = transform.position.y + (tMat.col1.y * m_p.x + tMat.col2.y * m_p.y); + aabb.lowerBound.Set(pX - m_radius, pY - m_radius); + aabb.upperBound.Set(pX + m_radius, pY + m_radius); + } + + /** + * @inheritDoc + */ + public override function ComputeMass(massData:b2MassData, density:Number) : void{ + massData.mass = density * b2Settings.b2_pi * m_radius * m_radius; + massData.center.SetV(m_p); + + // inertia about the local origin + //massData.I = massData.mass * (0.5 * m_radius * m_radius + b2Dot(m_p, m_p)); + massData.I = massData.mass * (0.5 * m_radius * m_radius + (m_p.x*m_p.x + m_p.y*m_p.y)); + } + + /** + * @inheritDoc + */ + public override function ComputeSubmergedArea( + normal:b2Vec2, + offset:Number, + xf:b2Transform, + c:b2Vec2):Number + { + var p:b2Vec2 = b2Math.MulX(xf, m_p); + var l:Number = -(b2Math.Dot(normal, p) - offset); + + if (l < -m_radius + Number.MIN_VALUE) + { + //Completely dry + return 0; + } + if (l > m_radius) + { + //Completely wet + c.SetV(p); + return Math.PI * m_radius * m_radius; + } + + //Magic + var r2:Number = m_radius * m_radius; + var l2:Number = l * l; + var area:Number = r2 *( Math.asin(l / m_radius) + Math.PI / 2) + l * Math.sqrt( r2 - l2 ); + var com:Number = -2 / 3 * Math.pow(r2 - l2, 1.5) / area; + + c.x = p.x + normal.x * com; + c.y = p.y + normal.y * com; + + return area; + } + + /** + * Get the local position of this circle in its parent body. + */ + public function GetLocalPosition() : b2Vec2{ + return m_p; + } + + /** + * Set the local position of this circle in its parent body. + */ + public function SetLocalPosition(position:b2Vec2):void { + m_p.SetV(position); + } + + /** + * Get the radius of the circle + */ + public function GetRadius():Number + { + return m_radius; + } + + /** + * Set the radius of the circle + */ + public function SetRadius(radius:Number):void + { + m_radius = radius; + } + + public function b2CircleShape(radius:Number = 0){ + super(); + m_type = e_circleShape; + m_radius = radius; + } + + // Local position in parent body + b2internal var m_p:b2Vec2 = new b2Vec2(); + +}; + +} diff --git a/srclib/Box2D/Collision/Shapes/b2EdgeChainDef.as b/srclib/Box2D/Collision/Shapes/b2EdgeChainDef.as new file mode 100644 index 00000000..96178cbe --- /dev/null +++ b/srclib/Box2D/Collision/Shapes/b2EdgeChainDef.as @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision.Shapes{ + + + + import Box2D.Common.b2internal; +use namespace b2internal; + + +/** +* This structure is used to build edge shapes. +* @see b2EdgeShape +*/ +public class b2EdgeChainDef// extends b2ShapeDef +{ + public function b2EdgeChainDef() + { + //type = b2Shape.e_edgeShape; + vertexCount = 0; + isALoop = true; + vertices = []; + } + + /** The vertices in local coordinates. */ + public var vertices: Array; + + /** The number of vertices in the chain. */ + public var vertexCount: int; + + /** Whether to create an extra edge between the first and last vertices. */ + public var isALoop: Boolean; +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/Shapes/b2EdgeShape.as b/srclib/Box2D/Collision/Shapes/b2EdgeShape.as new file mode 100644 index 00000000..53c70fc3 --- /dev/null +++ b/srclib/Box2D/Collision/Shapes/b2EdgeShape.as @@ -0,0 +1,410 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision.Shapes{ + + + + import Box2D.Collision.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; +use namespace b2internal; + + + +/** + * An edge shape. + * @private + * @see b2EdgeChainDef + */ +public class b2EdgeShape extends b2Shape +{ + /** + * Returns false. Edges cannot contain points. + */ + public override function TestPoint(transform:b2Transform, p:b2Vec2) : Boolean{ + return false; + } + + /** + * @inheritDoc + */ + public override function RayCast(output:b2RayCastOutput, input:b2RayCastInput, transform:b2Transform):Boolean + { + var tMat:b2Mat22; + var rX: Number = input.p2.x - input.p1.x; + var rY: Number = input.p2.y - input.p1.y; + + //b2Vec2 v1 = b2Mul(transform, m_v1); + tMat = transform.R; + var v1X: Number = transform.position.x + (tMat.col1.x * m_v1.x + tMat.col2.x * m_v1.y); + var v1Y: Number = transform.position.y + (tMat.col1.y * m_v1.x + tMat.col2.y * m_v1.y); + + //b2Vec2 n = b2Cross(d, 1.0); + var nX: Number = transform.position.y + (tMat.col1.y * m_v2.x + tMat.col2.y * m_v2.y) - v1Y; + var nY: Number = -(transform.position.x + (tMat.col1.x * m_v2.x + tMat.col2.x * m_v2.y) - v1X); + + var k_slop: Number = 100.0 * Number.MIN_VALUE; + var denom: Number = -(rX * nX + rY * nY); + + // Cull back facing collision and ignore parallel segments. + if (denom > k_slop) + { + // Does the segment intersect the infinite line associated with this segment? + var bX: Number = input.p1.x - v1X; + var bY: Number = input.p1.y - v1Y; + var a: Number = (bX * nX + bY * nY); + + if (0.0 <= a && a <= input.maxFraction * denom) + { + var mu2: Number = -rX * bY + rY * bX; + + // Does the segment intersect this segment? + if (-k_slop * denom <= mu2 && mu2 <= denom * (1.0 + k_slop)) + { + a /= denom; + output.fraction = a; + var nLen: Number = Math.sqrt(nX * nX + nY * nY); + output.normal.x = nX / nLen; + output.normal.y = nY / nLen; + return true; + } + } + } + + return false; + } + + /** + * @inheritDoc + */ + public override function ComputeAABB(aabb:b2AABB, transform:b2Transform) : void{ + var tMat:b2Mat22 = transform.R; + //b2Vec2 v1 = b2Mul(transform, m_v1); + var v1X:Number = transform.position.x + (tMat.col1.x * m_v1.x + tMat.col2.x * m_v1.y); + var v1Y:Number = transform.position.y + (tMat.col1.y * m_v1.x + tMat.col2.y * m_v1.y); + //b2Vec2 v2 = b2Mul(transform, m_v2); + var v2X:Number = transform.position.x + (tMat.col1.x * m_v2.x + tMat.col2.x * m_v2.y); + var v2Y:Number = transform.position.y + (tMat.col1.y * m_v2.x + tMat.col2.y * m_v2.y); + if (v1X < v2X) { + aabb.lowerBound.x = v1X; + aabb.upperBound.x = v2X; + } else { + aabb.lowerBound.x = v2X; + aabb.upperBound.x = v1X; + } + if (v1Y < v2Y) { + aabb.lowerBound.y = v1Y; + aabb.upperBound.y = v2Y; + } else { + aabb.lowerBound.y = v2Y; + aabb.upperBound.y = v1Y; + } + } + + /** + * @inheritDoc + */ + public override function ComputeMass(massData:b2MassData, density:Number) : void{ + massData.mass = 0; + massData.center.SetV(m_v1); + massData.I = 0; + } + + /** + * @inheritDoc + */ + public override function ComputeSubmergedArea( + normal:b2Vec2, + offset:Number, + xf:b2Transform, + c:b2Vec2):Number + { + // Note that v0 is independant of any details of the specific edge + // We are relying on v0 being consistent between multiple edges of the same body + //b2Vec2 v0 = offset * normal; + var v0:b2Vec2 = new b2Vec2(normal.x * offset, normal.y * offset); + + var v1:b2Vec2 = b2Math.MulX(xf, m_v1); + var v2:b2Vec2 = b2Math.MulX(xf, m_v2); + + var d1:Number = b2Math.Dot(normal, v1) - offset; + var d2:Number = b2Math.Dot(normal, v2) - offset; + if (d1 > 0) + { + if (d2 > 0) + { + return 0; + } + else + { + //v1 = -d2 / (d1 - d2) * v1 + d1 / (d1 - d2) * v2; + v1.x = -d2 / (d1 - d2) * v1.x + d1 / (d1 - d2) * v2.x; + v1.y = -d2 / (d1 - d2) * v1.y + d1 / (d1 - d2) * v2.y; + } + } + else + { + if (d2 > 0) + { + //v2 = -d2 / (d1 - d2) * v1 + d1 / (d1 - d2) * v2; + v2.x = -d2 / (d1 - d2) * v1.x + d1 / (d1 - d2) * v2.x; + v2.y = -d2 / (d1 - d2) * v1.y + d1 / (d1 - d2) * v2.y; + } + else + { + // Nothing + } + } + // v0,v1,v2 represents a fully submerged triangle + // Area weighted centroid + c.x = (v0.x + v1.x + v2.x) / 3; + c.y = (v0.y + v1.y + v2.y) / 3; + + //b2Vec2 e1 = v1 - v0; + //b2Vec2 e2 = v2 - v0; + //return 0.5f * b2Cross(e1, e2); + return 0.5 * ( (v1.x - v0.x) * (v2.y - v0.y) - (v1.y - v0.y) * (v2.x - v0.x) ); + } + + /** + * Get the distance from vertex1 to vertex2. + */ + public function GetLength(): Number + { + return m_length; + } + + /** + * Get the local position of vertex1 in parent body. + */ + public function GetVertex1(): b2Vec2 + { + return m_v1; + } + + /** + * Get the local position of vertex2 in parent body. + */ + public function GetVertex2(): b2Vec2 + { + return m_v2; + } + + /** + * Get a core vertex in local coordinates. These vertices + * represent a smaller edge that is used for time of impact + * computations. + */ + public function GetCoreVertex1(): b2Vec2 + { + return m_coreV1; + } + + /** + * Get a core vertex in local coordinates. These vertices + * represent a smaller edge that is used for time of impact + * computations. + */ + public function GetCoreVertex2(): b2Vec2 + { + return m_coreV2; + } + + /** + * Get a perpendicular unit vector, pointing + * from the solid side to the empty side. + */ + public function GetNormalVector(): b2Vec2 + { + return m_normal; + } + + + /** + * Get a parallel unit vector, pointing + * from vertex1 to vertex2. + */ + public function GetDirectionVector(): b2Vec2 + { + return m_direction; + } + + /** + * Returns a unit vector halfway between + * m_direction and m_prevEdge.m_direction. + */ + public function GetCorner1Vector(): b2Vec2 + { + return m_cornerDir1; + } + + /** + * Returns a unit vector halfway between + * m_direction and m_nextEdge.m_direction. + */ + public function GetCorner2Vector(): b2Vec2 + { + return m_cornerDir2; + } + + /** + * Returns true if the first corner of this edge + * bends towards the solid side. + */ + public function Corner1IsConvex(): Boolean + { + return m_cornerConvex1; + } + + /** + * Returns true if the second corner of this edge + * bends towards the solid side. + */ + public function Corner2IsConvex(): Boolean + { + return m_cornerConvex2; + } + + /** + * Get the first vertex and apply the supplied transform. + */ + public function GetFirstVertex(xf: b2Transform): b2Vec2 + { + //return b2Mul(xf, m_coreV1); + var tMat:b2Mat22 = xf.R; + return new b2Vec2(xf.position.x + (tMat.col1.x * m_coreV1.x + tMat.col2.x * m_coreV1.y), + xf.position.y + (tMat.col1.y * m_coreV1.x + tMat.col2.y * m_coreV1.y)); + } + + /** + * Get the next edge in the chain. + */ + public function GetNextEdge(): b2EdgeShape + { + return m_nextEdge; + } + + /** + * Get the previous edge in the chain. + */ + public function GetPrevEdge(): b2EdgeShape + { + return m_prevEdge; + } + + private var s_supportVec:b2Vec2 = new b2Vec2(); + /** + * Get the support point in the given world direction. + * Use the supplied transform. + */ + public function Support(xf:b2Transform, dX:Number, dY:Number) : b2Vec2{ + var tMat:b2Mat22 = xf.R; + //b2Vec2 v1 = b2Mul(xf, m_coreV1); + var v1X:Number = xf.position.x + (tMat.col1.x * m_coreV1.x + tMat.col2.x * m_coreV1.y); + var v1Y:Number = xf.position.y + (tMat.col1.y * m_coreV1.x + tMat.col2.y * m_coreV1.y); + + //b2Vec2 v2 = b2Mul(xf, m_coreV2); + var v2X:Number = xf.position.x + (tMat.col1.x * m_coreV2.x + tMat.col2.x * m_coreV2.y); + var v2Y:Number = xf.position.y + (tMat.col1.y * m_coreV2.x + tMat.col2.y * m_coreV2.y); + + if ((v1X * dX + v1Y * dY) > (v2X * dX + v2Y * dY)) { + s_supportVec.x = v1X; + s_supportVec.y = v1Y; + } else { + s_supportVec.x = v2X; + s_supportVec.y = v2Y; + } + return s_supportVec; + } + + //--------------- Internals Below ------------------- + + /** + * @private + */ + public function b2EdgeShape(v1: b2Vec2, v2: b2Vec2){ + super(); + m_type = e_edgeShape; + + m_prevEdge = null; + m_nextEdge = null; + + m_v1 = v1; + m_v2 = v2; + + m_direction.Set(m_v2.x - m_v1.x, m_v2.y - m_v1.y); + m_length = m_direction.Normalize(); + m_normal.Set(m_direction.y, -m_direction.x); + + m_coreV1.Set(-b2Settings.b2_toiSlop * (m_normal.x - m_direction.x) + m_v1.x, + -b2Settings.b2_toiSlop * (m_normal.y - m_direction.y) + m_v1.y) + m_coreV2.Set(-b2Settings.b2_toiSlop * (m_normal.x + m_direction.x) + m_v2.x, + -b2Settings.b2_toiSlop * (m_normal.y + m_direction.y) + m_v2.y) + + m_cornerDir1 = m_normal; + m_cornerDir2.Set(-m_normal.x, -m_normal.y); + } + + /** + * @private + */ + b2internal function SetPrevEdge(edge: b2EdgeShape, core: b2Vec2, cornerDir: b2Vec2, convex: Boolean): void + { + m_prevEdge = edge; + m_coreV1 = core; + m_cornerDir1 = cornerDir; + m_cornerConvex1 = convex; + } + + /** + * @private + */ + b2internal function SetNextEdge(edge: b2EdgeShape, core: b2Vec2, cornerDir: b2Vec2, convex: Boolean): void + { + m_nextEdge = edge; + m_coreV2 = core; + m_cornerDir2 = cornerDir; + m_cornerConvex2 = convex; + } + + b2internal var m_v1:b2Vec2 = new b2Vec2(); + b2internal var m_v2:b2Vec2 = new b2Vec2(); + + b2internal var m_coreV1:b2Vec2 = new b2Vec2(); + b2internal var m_coreV2:b2Vec2 = new b2Vec2(); + + b2internal var m_length:Number; + + b2internal var m_normal:b2Vec2 = new b2Vec2(); + + b2internal var m_direction:b2Vec2 = new b2Vec2(); + + b2internal var m_cornerDir1:b2Vec2 = new b2Vec2(); + + b2internal var m_cornerDir2:b2Vec2 = new b2Vec2(); + + b2internal var m_cornerConvex1:Boolean; + b2internal var m_cornerConvex2:Boolean; + + b2internal var m_nextEdge:b2EdgeShape; + b2internal var m_prevEdge:b2EdgeShape; + +}; + +} diff --git a/srclib/Box2D/Collision/Shapes/b2MassData.as b/srclib/Box2D/Collision/Shapes/b2MassData.as new file mode 100644 index 00000000..307eb963 --- /dev/null +++ b/srclib/Box2D/Collision/Shapes/b2MassData.as @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision.Shapes{ + + + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; +use namespace b2internal; + + +/** +* This holds the mass data computed for a shape. +*/ +public class b2MassData +{ + public function b2MassData() {} + + /** + * The mass of the shape, usually in kilograms. + */ + public var mass:Number = 0.0; + /** + * The position of the shape's centroid relative to the shape's origin. + */ + public var center:b2Vec2 = new b2Vec2(0,0); + /** + * The rotational inertia of the shape. + * This may be about the center or local origin, depending on usage. + */ + public var I:Number = 0.0; +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/Shapes/b2PolygonShape.as b/srclib/Box2D/Collision/Shapes/b2PolygonShape.as new file mode 100644 index 00000000..57e2c0cf --- /dev/null +++ b/srclib/Box2D/Collision/Shapes/b2PolygonShape.as @@ -0,0 +1,922 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision.Shapes{ + + import Box2D.Collision.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; +use namespace b2internal; + +/** +* Convex polygon. The vertices must be in CCW order for a right-handed +* coordinate system with the z-axis coming out of the screen. +* @see b2PolygonDef +*/ + +public class b2PolygonShape extends b2Shape +{ + public override function Copy():b2Shape + { + var s:b2PolygonShape = new b2PolygonShape(); + s.Set(this); + return s; + } + + public override function Set(other:b2Shape):void + { + super.Set(other); + if (other is b2PolygonShape) + { + var other2:b2PolygonShape = other as b2PolygonShape; + m_centroid.SetV(other2.m_centroid); + m_vertexCount = other2.m_vertexCount; + Reserve(m_vertexCount); + for (var i:int = 0; i < m_vertexCount; i++) + { + m_vertices[i].SetV(other2.m_vertices[i]); + m_normals[i].SetV(other2.m_normals[i]); + } + } + } + + /** + * Copy vertices. This assumes the vertices define a convex polygon. + * It is assumed that the exterior is the the right of each edge. + */ + public function SetAsArray(vertices:Array, vertexCount:Number = 0):void + { + var v:Vector. = new Vector.(); + for each(var tVec:b2Vec2 in vertices) + { + v.push(tVec); + } + SetAsVector(v, vertexCount); + } + + public static function AsArray(vertices:Array, vertexCount:Number):b2PolygonShape + { + var polygonShape:b2PolygonShape = new b2PolygonShape(); + polygonShape.SetAsArray(vertices, vertexCount); + return polygonShape; + } + + /** + * Copy vertices. This assumes the vertices define a convex polygon. + * It is assumed that the exterior is the the right of each edge. + */ + public function SetAsVector(vertices:Vector., vertexCount:Number = 0):void + { + if (vertexCount == 0) + vertexCount = vertices.length; + + b2Settings.b2Assert(2 <= vertexCount); + m_vertexCount = vertexCount; + + Reserve(vertexCount); + + var i:int; + + // Copy vertices + for (i = 0; i < m_vertexCount; i++) + { + m_vertices[i].SetV(vertices[i]); + } + + // Compute normals. Ensure the edges have non-zero length. + for (i = 0; i < m_vertexCount; ++i) + { + var i1:int = i; + var i2:int = i + 1 < m_vertexCount ? i + 1 : 0; + var edge:b2Vec2 = b2Math.SubtractVV(m_vertices[i2], m_vertices[i1]); + b2Settings.b2Assert(edge.LengthSquared() > Number.MIN_VALUE /* * Number.MIN_VALUE*/); + m_normals[i].SetV(b2Math.CrossVF(edge, 1.0)); + m_normals[i].Normalize(); + } + +//#ifdef _DEBUG + // Ensure the polygon is convex and the interior + // is to the left of each edge. + //for (int32 i = 0; i < m_vertexCount; ++i) + //{ + //int32 i1 = i; + //int32 i2 = i + 1 < m_vertexCount ? i + 1 : 0; + //b2Vec2 edge = m_vertices[i2] - m_vertices[i1]; + //for (int32 j = 0; j < m_vertexCount; ++j) + //{ + // Don't check vertices on the current edge. + //if (j == i1 || j == i2) + //{ + //continue; + //} + // + //b2Vec2 r = m_vertices[j] - m_vertices[i1]; + // Your polygon is non-convex (it has an indentation) or + // has colinear edges. + //float32 s = b2Cross(edge, r); + //b2Assert(s > 0.0f); + //} + //} +//#endif + + // Compute the polygon centroid + m_centroid = ComputeCentroid(m_vertices, m_vertexCount); + } + + public static function AsVector(vertices:Vector., vertexCount:Number):b2PolygonShape + { + var polygonShape:b2PolygonShape = new b2PolygonShape(); + polygonShape.SetAsVector(vertices, vertexCount); + return polygonShape; + } + + /** + * Build vertices to represent an axis-aligned box. + * @param hx the half-width. + * @param hy the half-height. + */ + public function SetAsBox(hx:Number, hy:Number) : void + { + m_vertexCount = 4; + Reserve(4); + m_vertices[0].Set(-hx, -hy); + m_vertices[1].Set( hx, -hy); + m_vertices[2].Set( hx, hy); + m_vertices[3].Set(-hx, hy); + m_normals[0].Set(0.0, -1.0); + m_normals[1].Set(1.0, 0.0); + m_normals[2].Set(0.0, 1.0); + m_normals[3].Set(-1.0, 0.0); + m_centroid.SetZero(); + } + + public static function AsBox(hx:Number, hy:Number):b2PolygonShape + { + var polygonShape:b2PolygonShape = new b2PolygonShape(); + polygonShape.SetAsBox(hx, hy); + return polygonShape; + } + + /** + * Build vertices to represent an oriented box. + * @param hx the half-width. + * @param hy the half-height. + * @param center the center of the box in local coordinates. + * @param angle the rotation of the box in local coordinates. + */ + static private var s_mat:b2Mat22 = new b2Mat22(); + public function SetAsOrientedBox(hx:Number, hy:Number, center:b2Vec2 = null, angle:Number = 0.0) : void + { + m_vertexCount = 4; + Reserve(4); + m_vertices[0].Set(-hx, -hy); + m_vertices[1].Set( hx, -hy); + m_vertices[2].Set( hx, hy); + m_vertices[3].Set(-hx, hy); + m_normals[0].Set(0.0, -1.0); + m_normals[1].Set(1.0, 0.0); + m_normals[2].Set(0.0, 1.0); + m_normals[3].Set(-1.0, 0.0); + m_centroid = center; + + var xf:b2Transform = new b2Transform(); + xf.position = center; + xf.R.Set(angle); + + // Transform vertices and normals. + for (var i:int = 0; i < m_vertexCount; ++i) + { + m_vertices[i] = b2Math.MulX(xf, m_vertices[i]); + m_normals[i] = b2Math.MulMV(xf.R, m_normals[i]); + } + } + + public static function AsOrientedBox(hx:Number, hy:Number, center:b2Vec2 = null, angle:Number = 0.0):b2PolygonShape + { + var polygonShape:b2PolygonShape = new b2PolygonShape(); + polygonShape.SetAsOrientedBox(hx, hy, center, angle); + return polygonShape; + } + + /** + * Set this as a single edge. + */ + public function SetAsEdge(v1:b2Vec2, v2:b2Vec2):void + { + m_vertexCount = 2; + Reserve(2); + m_vertices[0].SetV(v1); + m_vertices[1].SetV(v2); + m_centroid.x = 0.5 * (v1.x + v2.x); + m_centroid.y = 0.5 * (v1.y + v2.y); + m_normals[0] = b2Math.CrossVF(b2Math.SubtractVV(v2, v1), 1.0); + m_normals[0].Normalize(); + m_normals[1].x = -m_normals[0].x; + m_normals[1].y = -m_normals[0].y; + } + + /** + * Set this as a single edge. + */ + static public function AsEdge(v1:b2Vec2, v2:b2Vec2):b2PolygonShape + { + var polygonShape:b2PolygonShape = new b2PolygonShape(); + polygonShape.SetAsEdge(v1, v2); + return polygonShape; + } + + + /** + * @inheritDoc + */ + public override function TestPoint(xf:b2Transform, p:b2Vec2) : Boolean{ + var tVec:b2Vec2; + + //b2Vec2 pLocal = b2MulT(xf.R, p - xf.position); + var tMat:b2Mat22 = xf.R; + var tX:Number = p.x - xf.position.x; + var tY:Number = p.y - xf.position.y; + var pLocalX:Number = (tX*tMat.col1.x + tY*tMat.col1.y); + var pLocalY:Number = (tX*tMat.col2.x + tY*tMat.col2.y); + + for (var i:int = 0; i < m_vertexCount; ++i) + { + //float32 dot = b2Dot(m_normals[i], pLocal - m_vertices[i]); + tVec = m_vertices[i]; + tX = pLocalX - tVec.x; + tY = pLocalY - tVec.y; + tVec = m_normals[i]; + var dot:Number = (tVec.x * tX + tVec.y * tY); + if (dot > 0.0) + { + return false; + } + } + + return true; + } + + /** + * @inheritDoc + */ + public override function RayCast(output:b2RayCastOutput, input:b2RayCastInput, transform:b2Transform):Boolean + { + var lower:Number = 0.0; + var upper:Number = input.maxFraction; + + var tX:Number; + var tY:Number; + var tMat:b2Mat22; + var tVec:b2Vec2; + + // Put the ray into the polygon's frame of reference. (AS3 Port Manual inlining follows) + //b2Vec2 p1 = b2MulT(transform.R, segment.p1 - transform.position); + tX = input.p1.x - transform.position.x; + tY = input.p1.y - transform.position.y; + tMat = transform.R; + var p1X:Number = (tX * tMat.col1.x + tY * tMat.col1.y); + var p1Y:Number = (tX * tMat.col2.x + tY * tMat.col2.y); + //b2Vec2 p2 = b2MulT(transform.R, segment.p2 - transform.position); + tX = input.p2.x - transform.position.x; + tY = input.p2.y - transform.position.y; + tMat = transform.R; + var p2X:Number = (tX * tMat.col1.x + tY * tMat.col1.y); + var p2Y:Number = (tX * tMat.col2.x + tY * tMat.col2.y); + //b2Vec2 d = p2 - p1; + var dX:Number = p2X - p1X; + var dY:Number = p2Y - p1Y; + var index:int = -1; + + for (var i:int = 0; i < m_vertexCount; ++i) + { + // p = p1 + a * d + // dot(normal, p - v) = 0 + // dot(normal, p1 - v) + a * dot(normal, d) = 0 + + //float32 numerator = b2Dot(m_normals[i], m_vertices[i] - p1); + tVec = m_vertices[i]; + tX = tVec.x - p1X; + tY = tVec.y - p1Y; + tVec = m_normals[i]; + var numerator:Number = (tVec.x*tX + tVec.y*tY); + //float32 denominator = b2Dot(m_normals[i], d); + var denominator:Number = (tVec.x * dX + tVec.y * dY); + + if (denominator == 0.0) + { + if (numerator < 0.0) + { + return false; + } + } + else + { + // Note: we want this predicate without division: + // lower < numerator / denominator, where denominator < 0 + // Since denominator < 0, we have to flip the inequality: + // lower < numerator / denominator <==> denominator * lower > numerator. + if (denominator < 0.0 && numerator < lower * denominator) + { + // Increase lower. + // The segment enters this half-space. + lower = numerator / denominator; + index = i; + } + else if (denominator > 0.0 && numerator < upper * denominator) + { + // Decrease upper. + // The segment exits this half-space. + upper = numerator / denominator; + } + } + + if (upper < lower - Number.MIN_VALUE) + { + return false; + } + } + + //b2Settings.b2Assert(0.0 <= lower && lower <= input.maxLambda); + + if (index >= 0) + { + output.fraction = lower; + //output.normal = b2Mul(transform.R, m_normals[index]); + tMat = transform.R; + tVec = m_normals[index]; + output.normal.x = (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + output.normal.y = (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + return true; + } + + return false; + } + + + /** + * @inheritDoc + */ + public override function ComputeAABB(aabb:b2AABB, xf:b2Transform) : void + { + //var lower:b2Vec2 = b2Math.MulX(xf, m_vertices[0]); + var tMat:b2Mat22 = xf.R; + var tVec:b2Vec2 = m_vertices[0]; + var lowerX:Number = xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var lowerY:Number = xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + var upperX:Number = lowerX; + var upperY:Number = lowerY; + + for (var i:int = 1; i < m_vertexCount; ++i) + { + tVec = m_vertices[i]; + var vX:Number = xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var vY:Number = xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + lowerX = lowerX < vX ? lowerX : vX; + lowerY = lowerY < vY ? lowerY : vY; + upperX = upperX > vX ? upperX : vX; + upperY = upperY > vY ? upperY : vY; + } + + aabb.lowerBound.x = lowerX - m_radius; + aabb.lowerBound.y = lowerY - m_radius; + aabb.upperBound.x = upperX + m_radius; + aabb.upperBound.y = upperY + m_radius; + } + + + /** + * @inheritDoc + */ + public override function ComputeMass(massData:b2MassData, density:Number) : void{ + // Polygon mass, centroid, and inertia. + // Let rho be the polygon density in mass per unit area. + // Then: + // mass = rho * int(dA) + // centroid.x = (1/mass) * rho * int(x * dA) + // centroid.y = (1/mass) * rho * int(y * dA) + // I = rho * int((x*x + y*y) * dA) + // + // We can compute these integrals by summing all the integrals + // for each triangle of the polygon. To evaluate the integral + // for a single triangle, we make a change of variables to + // the (u,v) coordinates of the triangle: + // x = x0 + e1x * u + e2x * v + // y = y0 + e1y * u + e2y * v + // where 0 <= u && 0 <= v && u + v <= 1. + // + // We integrate u from [0,1-v] and then v from [0,1]. + // We also need to use the Jacobian of the transformation: + // D = cross(e1, e2) + // + // Simplification: triangle centroid = (1/3) * (p1 + p2 + p3) + // + // The rest of the derivation is handled by computer algebra. + + //b2Settings.b2Assert(m_vertexCount >= 2); + + // A line segment has zero mass. + if (m_vertexCount == 2) + { + massData.center.x = 0.5 * (m_vertices[0].x + m_vertices[1].x); + massData.center.y = 0.5 * (m_vertices[0].y + m_vertices[1].y); + massData.mass = 0.0; + massData.I = 0.0; + return; + } + + //b2Vec2 center; center.Set(0.0f, 0.0f); + var centerX:Number = 0.0; + var centerY:Number = 0.0; + var area:Number = 0.0; + var I:Number = 0.0; + + // pRef is the reference point for forming triangles. + // It's location doesn't change the result (except for rounding error). + //b2Vec2 pRef(0.0f, 0.0f); + var p1X:Number = 0.0; + var p1Y:Number = 0.0; + /*#if 0 + // This code would put the reference point inside the polygon. + for (int32 i = 0; i < m_vertexCount; ++i) + { + pRef += m_vertices[i]; + } + pRef *= 1.0f / count; + #endif*/ + + var k_inv3:Number = 1.0 / 3.0; + + for (var i:int = 0; i < m_vertexCount; ++i) + { + // Triangle vertices. + //b2Vec2 p1 = pRef; + // + //b2Vec2 p2 = m_vertices[i]; + var p2:b2Vec2 = m_vertices[i]; + //b2Vec2 p3 = i + 1 < m_vertexCount ? m_vertices[i+1] : m_vertices[0]; + var p3:b2Vec2 = i + 1 < m_vertexCount ? m_vertices[int(i+1)] : m_vertices[0]; + + //b2Vec2 e1 = p2 - p1; + var e1X:Number = p2.x - p1X; + var e1Y:Number = p2.y - p1Y; + //b2Vec2 e2 = p3 - p1; + var e2X:Number = p3.x - p1X; + var e2Y:Number = p3.y - p1Y; + + //float32 D = b2Cross(e1, e2); + var D:Number = e1X * e2Y - e1Y * e2X; + + //float32 triangleArea = 0.5f * D; + var triangleArea:Number = 0.5 * D; + area += triangleArea; + + // Area weighted centroid + //center += triangleArea * k_inv3 * (p1 + p2 + p3); + centerX += triangleArea * k_inv3 * (p1X + p2.x + p3.x); + centerY += triangleArea * k_inv3 * (p1Y + p2.y + p3.y); + + //float32 px = p1.x, py = p1.y; + var px:Number = p1X; + var py:Number = p1Y; + //float32 ex1 = e1.x, ey1 = e1.y; + var ex1:Number = e1X; + var ey1:Number = e1Y; + //float32 ex2 = e2.x, ey2 = e2.y; + var ex2:Number = e2X; + var ey2:Number = e2Y; + + //float32 intx2 = k_inv3 * (0.25f * (ex1*ex1 + ex2*ex1 + ex2*ex2) + (px*ex1 + px*ex2)) + 0.5f*px*px; + var intx2:Number = k_inv3 * (0.25 * (ex1*ex1 + ex2*ex1 + ex2*ex2) + (px*ex1 + px*ex2)) + 0.5*px*px; + //float32 inty2 = k_inv3 * (0.25f * (ey1*ey1 + ey2*ey1 + ey2*ey2) + (py*ey1 + py*ey2)) + 0.5f*py*py; + var inty2:Number = k_inv3 * (0.25 * (ey1*ey1 + ey2*ey1 + ey2*ey2) + (py*ey1 + py*ey2)) + 0.5*py*py; + + I += D * (intx2 + inty2); + } + + // Total mass + massData.mass = density * area; + + // Center of mass + //b2Settings.b2Assert(area > Number.MIN_VALUE); + //center *= 1.0f / area; + centerX *= 1.0 / area; + centerY *= 1.0 / area; + //massData->center = center; + massData.center.Set(centerX, centerY); + + // Inertia tensor relative to the local origin. + massData.I = density * I; + } + + /** + * @inheritDoc + */ + public override function ComputeSubmergedArea( + normal:b2Vec2, + offset:Number, + xf:b2Transform, + c:b2Vec2):Number + { + // Transform plane into shape co-ordinates + var normalL:b2Vec2 = b2Math.MulTMV(xf.R, normal); + var offsetL:Number = offset - b2Math.Dot(normal, xf.position); + + var depths:Vector. = new Vector.(); + var diveCount:int = 0; + var intoIndex:int = -1; + var outoIndex:int = -1; + + var lastSubmerged:Boolean = false; + var i:int; + for (i = 0; i < m_vertexCount;++i) + { + depths[i] = b2Math.Dot(normalL, m_vertices[i]) - offsetL; + var isSubmerged:Boolean = depths[i] < -Number.MIN_VALUE; + if (i > 0) + { + if (isSubmerged) + { + if (!lastSubmerged) + { + intoIndex = i - 1; + diveCount++; + } + } + else + { + if (lastSubmerged) + { + outoIndex = i - 1; + diveCount++; + } + } + } + lastSubmerged = isSubmerged; + } + switch(diveCount) + { + case 0: + if (lastSubmerged ) + { + // Completely submerged + var md:b2MassData = new b2MassData(); + ComputeMass(md, 1); + c.SetV(b2Math.MulX(xf, md.center)); + return md.mass; + } + else + { + //Completely dry + return 0; + } + break; + case 1: + if (intoIndex == -1) + { + intoIndex = m_vertexCount - 1; + } + else + { + outoIndex = m_vertexCount - 1; + } + break; + } + var intoIndex2:int = (intoIndex + 1) % m_vertexCount; + var outoIndex2:int = (outoIndex + 1) % m_vertexCount; + var intoLamdda:Number = (0 - depths[intoIndex]) / (depths[intoIndex2] - depths[intoIndex]); + var outoLamdda:Number = (0 - depths[outoIndex]) / (depths[outoIndex2] - depths[outoIndex]); + + var intoVec:b2Vec2 = new b2Vec2(m_vertices[intoIndex].x * (1 - intoLamdda) + m_vertices[intoIndex2].x * intoLamdda, + m_vertices[intoIndex].y * (1 - intoLamdda) + m_vertices[intoIndex2].y * intoLamdda); + var outoVec:b2Vec2 = new b2Vec2(m_vertices[outoIndex].x * (1 - outoLamdda) + m_vertices[outoIndex2].x * outoLamdda, + m_vertices[outoIndex].y * (1 - outoLamdda) + m_vertices[outoIndex2].y * outoLamdda); + + // Initialize accumulator + var area:Number = 0; + var center:b2Vec2 = new b2Vec2(); + var p2:b2Vec2 = m_vertices[intoIndex2]; + var p3:b2Vec2; + + // An awkward loop from intoIndex2+1 to outIndex2 + i = intoIndex2; + while (i != outoIndex2) + { + i = (i + 1) % m_vertexCount; + if(i == outoIndex2) + p3 = outoVec + else + p3 = m_vertices[i]; + + var triangleArea:Number = 0.5 * ( (p2.x - intoVec.x) * (p3.y - intoVec.y) - (p2.y - intoVec.y) * (p3.x - intoVec.x) ); + area += triangleArea; + // Area weighted centroid + center.x += triangleArea * (intoVec.x + p2.x + p3.x) / 3; + center.y += triangleArea * (intoVec.y + p2.y + p3.y) / 3; + + p2 = p3; + } + + //Normalize and transform centroid + center.Multiply(1 / area); + c.SetV(b2Math.MulX(xf, center)); + + return area; + } + + /** + * Get the vertex count. + */ + public function GetVertexCount() : int{ + return m_vertexCount; + } + + /** + * Get the vertices in local coordinates. + */ + public function GetVertices() : Vector.{ + return m_vertices; + } + + /** + * Get the edge normal vectors. There is one for each vertex. + */ + public function GetNormals() : Vector. + { + return m_normals; + } + + /** + * Get the supporting vertex index in the given direction. + */ + public function GetSupport(d:b2Vec2):int + { + var bestIndex:int = 0; + var bestValue:Number = m_vertices[0].x * d.x + m_vertices[0].y * d.y; + for (var i:int= 1; i < m_vertexCount; ++i) + { + var value:Number = m_vertices[i].x * d.x + m_vertices[i].y * d.y; + if (value > bestValue) + { + bestIndex = i; + bestValue = value; + } + } + return bestIndex; + } + + public function GetSupportVertex(d:b2Vec2):b2Vec2 + { + var bestIndex:int = 0; + var bestValue:Number = m_vertices[0].x * d.x + m_vertices[0].y * d.y; + for (var i:int= 1; i < m_vertexCount; ++i) + { + var value:Number = m_vertices[i].x * d.x + m_vertices[i].y * d.y; + if (value > bestValue) + { + bestIndex = i; + bestValue = value; + } + } + return m_vertices[bestIndex]; + } + + // TODO: Expose this + private function Validate():Boolean + { + /* + // Ensure the polygon is convex. + for (int32 i = 0; i < m_vertexCount; ++i) + { + for (int32 j = 0; j < m_vertexCount; ++j) + { + // Don't check vertices on the current edge. + if (j == i || j == (i + 1) % m_vertexCount) + { + continue; + } + + // Your polygon is non-convex (it has an indentation). + // Or your polygon is too skinny. + float32 s = b2Dot(m_normals[i], m_vertices[j] - m_vertices[i]); + b2Assert(s < -b2_linearSlop); + } + } + + // Ensure the polygon is counter-clockwise. + for (i = 1; i < m_vertexCount; ++i) + { + var cross:Number = b2Math.b2CrossVV(m_normals[int(i-1)], m_normals[i]); + + // Keep asinf happy. + cross = b2Math.b2Clamp(cross, -1.0, 1.0); + + // You have consecutive edges that are almost parallel on your polygon. + var angle:Number = Math.asin(cross); + //b2Assert(angle > b2_angularSlop); + trace(angle > b2Settings.b2_angularSlop); + } + */ + return false; + } + //--------------- Internals Below ------------------- + + /** + * @private + */ + public function b2PolygonShape(){ + + //b2Settings.b2Assert(def.type == e_polygonShape); + m_type = e_polygonShape; + + m_centroid = new b2Vec2(); + m_vertices = new Vector.(); + m_normals = new Vector.(); + } + + private function Reserve(count:int):void + { + for (var i:int = m_vertices.length; i < count; i++) + { + m_vertices[i] = new b2Vec2(); + m_normals[i] = new b2Vec2(); + } + } + + // Local position of the polygon centroid. + b2internal var m_centroid:b2Vec2; + + b2internal var m_vertices:Vector.; + b2internal var m_normals:Vector.; + + b2internal var m_vertexCount:int; + + + + /** + * Computes the centroid of the given polygon + * @param vs vector of b2Vec specifying a polygon + * @param count length of vs + * @return the polygon centroid + */ + static public function ComputeCentroid(vs:Vector., count:uint) : b2Vec2 + { + //b2Settings.b2Assert(count >= 3); + + //b2Vec2 c; c.Set(0.0f, 0.0f); + var c:b2Vec2 = new b2Vec2(); + var area:Number = 0.0; + + // pRef is the reference point for forming triangles. + // It's location doesn't change the result (except for rounding error). + //b2Vec2 pRef(0.0f, 0.0f); + var p1X:Number = 0.0; + var p1Y:Number = 0.0; + /*#if 0 + // This code would put the reference point inside the polygon. + for (int32 i = 0; i < count; ++i) + { + pRef += vs[i]; + } + pRef *= 1.0f / count; + #endif*/ + + var inv3:Number = 1.0 / 3.0; + + for (var i:int = 0; i < count; ++i) + { + // Triangle vertices. + //b2Vec2 p1 = pRef; + // 0.0, 0.0 + //b2Vec2 p2 = vs[i]; + var p2:b2Vec2 = vs[i]; + //b2Vec2 p3 = i + 1 < count ? vs[i+1] : vs[0]; + var p3:b2Vec2 = i + 1 < count ? vs[int(i+1)] : vs[0]; + + //b2Vec2 e1 = p2 - p1; + var e1X:Number = p2.x - p1X; + var e1Y:Number = p2.y - p1Y; + //b2Vec2 e2 = p3 - p1; + var e2X:Number = p3.x - p1X; + var e2Y:Number = p3.y - p1Y; + + //float32 D = b2Cross(e1, e2); + var D:Number = (e1X * e2Y - e1Y * e2X); + + //float32 triangleArea = 0.5f * D; + var triangleArea:Number = 0.5 * D; + area += triangleArea; + + // Area weighted centroid + //c += triangleArea * inv3 * (p1 + p2 + p3); + c.x += triangleArea * inv3 * (p1X + p2.x + p3.x); + c.y += triangleArea * inv3 * (p1Y + p2.y + p3.y); + } + + // Centroid + //beSettings.b2Assert(area > Number.MIN_VALUE); + //c *= 1.0 / area; + c.x *= 1.0 / area; + c.y *= 1.0 / area; + return c; + } + + /** + * Computes a polygon's OBB + * @see http://www.geometrictools.com/Documentation/MinimumAreaRectangle.pdf + */ + static b2internal function ComputeOBB(obb:b2OBB, vs:Vector., count:int) : void + { + var i:int; + var p:Vector. = new Vector.(count + 1); + for (i = 0; i < count; ++i) + { + p[i] = vs[i]; + } + p[count] = p[0]; + + var minArea:Number = Number.MAX_VALUE; + + for (i = 1; i <= count; ++i) + { + var root:b2Vec2 = p[int(i-1)]; + //b2Vec2 ux = p[i] - root; + var uxX:Number = p[i].x - root.x; + var uxY:Number = p[i].y - root.y; + //var length:Number = ux.Normalize(); + var length:Number = Math.sqrt(uxX*uxX + uxY*uxY); + uxX /= length; + uxY /= length; + //b2Settings.b2Assert(length > Number.MIN_VALUE); + //b2Vec2 uy(-ux.y, ux.x); + var uyX:Number = -uxY; + var uyY:Number = uxX; + //b2Vec2 lower(FLT_MAX, FLT_MAX); + var lowerX:Number = Number.MAX_VALUE; + var lowerY:Number = Number.MAX_VALUE; + //b2Vec2 upper(-FLT_MAX, -FLT_MAX); + var upperX:Number = -Number.MAX_VALUE; + var upperY:Number = -Number.MAX_VALUE; + + for (var j:int = 0; j < count; ++j) + { + //b2Vec2 d = p[j] - root; + var dX:Number = p[j].x - root.x; + var dY:Number = p[j].y - root.y; + //b2Vec2 r; + //var rX:Number = b2Dot(ux, d); + var rX:Number = (uxX*dX + uxY*dY); + //var rY:Number = b2Dot(uy, d); + var rY:Number = (uyX*dX + uyY*dY); + //lower = b2Min(lower, r); + if (rX < lowerX) lowerX = rX; + if (rY < lowerY) lowerY = rY; + //upper = b2Max(upper, r); + if (rX > upperX) upperX = rX; + if (rY > upperY) upperY = rY; + } + + var area:Number = (upperX - lowerX) * (upperY - lowerY); + if (area < 0.95 * minArea) + { + minArea = area; + //obb->R.col1 = ux; + obb.R.col1.x = uxX; + obb.R.col1.y = uxY; + //obb->R.col2 = uy; + obb.R.col2.x = uyX; + obb.R.col2.y = uyY; + //b2Vec2 center = 0.5f * (lower + upper); + var centerX:Number = 0.5 * (lowerX + upperX); + var centerY:Number = 0.5 * (lowerY + upperY); + //obb->center = root + b2Mul(obb->R, center); + var tMat:b2Mat22 = obb.R; + obb.center.x = root.x + (tMat.col1.x * centerX + tMat.col2.x * centerY); + obb.center.y = root.y + (tMat.col1.y * centerX + tMat.col2.y * centerY); + //obb->extents = 0.5f * (upper - lower); + obb.extents.x = 0.5 * (upperX - lowerX); + obb.extents.y = 0.5 * (upperY - lowerY); + } + } + + //b2Settings.b2Assert(minArea < Number.MAX_VALUE); + } + + +}; + +} diff --git a/srclib/Box2D/Collision/Shapes/b2Shape.as b/srclib/Box2D/Collision/Shapes/b2Shape.as new file mode 100644 index 00000000..a5204393 --- /dev/null +++ b/srclib/Box2D/Collision/Shapes/b2Shape.as @@ -0,0 +1,171 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision.Shapes{ + + + + + import Box2D.Collision.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; +use namespace b2internal; + + + +/** +* A shape is used for collision detection. Shapes are created in b2Body. +* You can use shape for collision detection before they are attached to the world. +* @warning you cannot reuse shapes. +*/ +public class b2Shape +{ + + /** + * Clone the shape + */ + virtual public function Copy():b2Shape + { + //var s:b2Shape = new b2Shape(); + //s.Set(this); + //return s; + return null; // Abstract type + } + + /** + * Assign the properties of anther shape to this + */ + virtual public function Set(other:b2Shape):void + { + //Don't copy m_type? + //m_type = other.m_type; + m_radius = other.m_radius; + } + + /** + * Get the type of this shape. You can use this to down cast to the concrete shape. + * @return the shape type. + */ + public function GetType() : int + { + return m_type; + } + + /** + * Test a point for containment in this shape. This only works for convex shapes. + * @param xf the shape world transform. + * @param p a point in world coordinates. + */ + public virtual function TestPoint(xf:b2Transform, p:b2Vec2) : Boolean {return false}; + + /** + * Cast a ray against this shape. + * @param output the ray-cast results. + * @param input the ray-cast input parameters. + * @param transform the transform to be applied to the shape. + */ + public virtual function RayCast(output:b2RayCastOutput, input:b2RayCastInput, transform:b2Transform):Boolean + { + return false; + } + + /** + * Given a transform, compute the associated axis aligned bounding box for this shape. + * @param aabb returns the axis aligned box. + * @param xf the world transform of the shape. + */ + public virtual function ComputeAABB(aabb:b2AABB, xf:b2Transform) : void {}; + + /** + * Compute the mass properties of this shape using its dimensions and density. + * The inertia tensor is computed about the local origin, not the centroid. + * @param massData returns the mass data for this shape. + */ + public virtual function ComputeMass(massData:b2MassData, density:Number) : void { }; + + /** + * Compute the volume and centroid of this shape intersected with a half plane + * @param normal the surface normal + * @param offset the surface offset along normal + * @param xf the shape transform + * @param c returns the centroid + * @return the total volume less than offset along normal + */ + public virtual function ComputeSubmergedArea( + normal:b2Vec2, + offset:Number, + xf:b2Transform, + c:b2Vec2):Number { return 0; }; + + public static function TestOverlap(shape1:b2Shape, transform1:b2Transform, shape2:b2Shape, transform2:b2Transform):Boolean + { + var input:b2DistanceInput = new b2DistanceInput(); + input.proxyA = new b2DistanceProxy(); + input.proxyA.Set(shape1); + input.proxyB = new b2DistanceProxy(); + input.proxyB.Set(shape2); + input.transformA = transform1; + input.transformB = transform2; + input.useRadii = true; + var simplexCache:b2SimplexCache = new b2SimplexCache(); + simplexCache.count = 0; + var output:b2DistanceOutput = new b2DistanceOutput(); + b2Distance.Distance(output, simplexCache, input); + return output.distance < 10.0 * Number.MIN_VALUE; + } + + //--------------- Internals Below ------------------- + /** + * @private + */ + public function b2Shape() + { + m_type = e_unknownShape; + m_radius = b2Settings.b2_linearSlop; + } + + //virtual ~b2Shape(); + + b2internal var m_type:int; + b2internal var m_radius:Number; + + /** + * The various collision shape types supported by Box2D. + */ + //enum b2ShapeType + //{ + static b2internal const e_unknownShape:int = -1; + static b2internal const e_circleShape:int = 0; + static b2internal const e_polygonShape:int = 1; + static b2internal const e_edgeShape:int = 2; + static b2internal const e_shapeTypeCount:int = 3; + //}; + + /** + * Possible return values for TestSegment + */ + /** Return value for TestSegment indicating a hit. */ + static public const e_hitCollide:int = 1; + /** Return value for TestSegment indicating a miss. */ + static public const e_missCollide:int = 0; + /** Return value for TestSegment indicating that the segment starting point, p1, is already inside the shape. */ + static public const e_startsInsideCollide:int = -1; +}; + + +} diff --git a/srclib/Box2D/Collision/b2AABB.as b/srclib/Box2D/Collision/b2AABB.as new file mode 100644 index 00000000..2d695f50 --- /dev/null +++ b/srclib/Box2D/Collision/b2AABB.as @@ -0,0 +1,225 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + +use namespace b2internal; + +/** +* An axis aligned bounding box. +*/ +public class b2AABB +{ + + public function b2AABB() {} + + /** + * Verify that the bounds are sorted. + */ + public function IsValid():Boolean{ + //b2Vec2 d = upperBound - lowerBound;; + var dX:Number = upperBound.x - lowerBound.x; + var dY:Number = upperBound.y - lowerBound.y; + var valid:Boolean = dX >= 0.0 && dY >= 0.0; + valid = valid && lowerBound.IsValid() && upperBound.IsValid(); + return valid; + } + + /** Get the center of the AABB. */ + public function GetCenter():b2Vec2 + { + return new b2Vec2( (lowerBound.x + upperBound.x) / 2, + (lowerBound.y + upperBound.y) / 2); + } + + /** Get the extents of the AABB (half-widths). */ + public function GetExtents():b2Vec2 + { + return new b2Vec2( (upperBound.x - lowerBound.x) / 2, + (upperBound.y - lowerBound.y) / 2); + } + + /** + * Is an AABB contained within this one. + */ + public function Contains(aabb:b2AABB):Boolean + { + var result:Boolean = true; + result &&= lowerBound.x <= aabb.lowerBound.x; + result &&= lowerBound.y <= aabb.lowerBound.y; + result &&= aabb.upperBound.x <= upperBound.x; + result &&= aabb.upperBound.y <= upperBound.y; + return result; + } + + // From Real-time Collision Detection, p179. + /** + * Perform a precise raycast against the AABB. + */ + public function RayCast(output:b2RayCastOutput, input:b2RayCastInput):Boolean + { + var tmin:Number = -Number.MAX_VALUE; + var tmax:Number = Number.MAX_VALUE; + + var pX:Number = input.p1.x; + var pY:Number = input.p1.y; + var dX:Number = input.p2.x - input.p1.x; + var dY:Number = input.p2.y - input.p1.y; + var absDX:Number = Math.abs(dX); + var absDY:Number = Math.abs(dY); + + var normal:b2Vec2 = output.normal; + + var inv_d:Number; + var t1:Number; + var t2:Number; + var t3:Number; + var s:Number; + + //x + { + if (absDX < Number.MIN_VALUE) + { + // Parallel. + if (pX < lowerBound.x || upperBound.x < pX) + return false; + } + else + { + inv_d = 1.0 / dX; + t1 = (lowerBound.x - pX) * inv_d; + t2 = (upperBound.x - pX) * inv_d; + + // Sign of the normal vector + s = -1.0; + + if (t1 > t2) + { + t3 = t1; + t1 = t2; + t2 = t3; + s = 1.0; + } + + // Push the min up + if (t1 > tmin) + { + normal.x = s; + normal.y = 0; + tmin = t1; + } + + // Pull the max down + tmax = Math.min(tmax, t2); + + if (tmin > tmax) + return false; + } + } + //y + { + if (absDY < Number.MIN_VALUE) + { + // Parallel. + if (pY < lowerBound.y || upperBound.y < pY) + return false; + } + else + { + inv_d = 1.0 / dY; + t1 = (lowerBound.y - pY) * inv_d; + t2 = (upperBound.y - pY) * inv_d; + + // Sign of the normal vector + s = -1.0; + + if (t1 > t2) + { + t3 = t1; + t1 = t2; + t2 = t3; + s = 1.0; + } + + // Push the min up + if (t1 > tmin) + { + normal.y = s; + normal.x = 0; + tmin = t1; + } + + // Pull the max down + tmax = Math.min(tmax, t2); + + if (tmin > tmax) + return false; + } + } + + output.fraction = tmin; + return true; + } + + /** + * Tests if another AABB overlaps this one. + */ + public function TestOverlap(other:b2AABB):Boolean + { + var d1X:Number = other.lowerBound.x - upperBound.x; + var d1Y:Number = other.lowerBound.y - upperBound.y; + var d2X:Number = lowerBound.x - other.upperBound.x; + var d2Y:Number = lowerBound.y - other.upperBound.y; + + if (d1X > 0.0 || d1Y > 0.0) + return false; + + if (d2X > 0.0 || d2Y > 0.0) + return false; + + return true; + } + + /** Combine two AABBs into one. */ + public static function Combine(aabb1:b2AABB, aabb2:b2AABB):b2AABB + { + var aabb:b2AABB = new b2AABB(); + aabb.Combine(aabb1, aabb2); + return aabb; + } + + /** Combine two AABBs into one. */ + public function Combine(aabb1:b2AABB, aabb2:b2AABB):void + { + lowerBound.x = Math.min(aabb1.lowerBound.x, aabb2.lowerBound.x); + lowerBound.y = Math.min(aabb1.lowerBound.y, aabb2.lowerBound.y); + upperBound.x = Math.max(aabb1.upperBound.x, aabb2.upperBound.x); + upperBound.y = Math.max(aabb1.upperBound.y, aabb2.upperBound.y); + } + + /** The lower vertex */ + public var lowerBound:b2Vec2 = new b2Vec2(); + /** The upper vertex */ + public var upperBound:b2Vec2 = new b2Vec2(); +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2Bound.as b/srclib/Box2D/Collision/b2Bound.as new file mode 100644 index 00000000..d463caf7 --- /dev/null +++ b/srclib/Box2D/Collision/b2Bound.as @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + +/** +* @private +*/ +public class b2Bound{ + + public function b2Bound() {} + + public function IsLower():Boolean { return (value & 1) == 0; } + public function IsUpper():Boolean { return (value & 1) == 1; } + public function Swap(b:b2Bound) : void{ + var tempValue:uint = value; + var tempProxy:b2Proxy = proxy; + var tempStabbingCount:uint = stabbingCount; + + value = b.value; + proxy = b.proxy; + stabbingCount = b.stabbingCount; + + b.value = tempValue; + b.proxy = tempProxy; + b.stabbingCount = tempStabbingCount; + } + + public var value:uint; + public var proxy:b2Proxy; + public var stabbingCount:uint; +} + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2BoundValues.as b/srclib/Box2D/Collision/b2BoundValues.as new file mode 100644 index 00000000..394f3d98 --- /dev/null +++ b/srclib/Box2D/Collision/b2BoundValues.as @@ -0,0 +1,41 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + +/** +* @private +*/ +public class b2BoundValues { + public function b2BoundValues() + { + lowerValues = new Vector.(); + lowerValues[0] = 0.0; + lowerValues[1] = 0.0; + upperValues = new Vector.(); + upperValues[0] = 0.0; + upperValues[1] = 0.0; + } + + public var lowerValues:Vector.; + public var upperValues:Vector.; +} + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2BroadPhase.as b/srclib/Box2D/Collision/b2BroadPhase.as new file mode 100644 index 00000000..2183e8a8 --- /dev/null +++ b/srclib/Box2D/Collision/b2BroadPhase.as @@ -0,0 +1,1089 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.*; + import Box2D.Common.Math.*; + + +use namespace b2internal; + + +/* +This broad phase uses the Sweep and Prune algorithm as described in: +Collision Detection in Interactive 3D Environments by Gino van den Bergen +Also, some ideas, such as using integral values for fast compares comes from +Bullet (http:/www.bulletphysics.com). +*/ + + +// Notes: +// - we use bound arrays instead of linked lists for cache coherence. +// - we use quantized integral values for fast compares. +// - we use short indices rather than pointers to save memory. +// - we use a stabbing count for fast overlap queries (less than order N). +// - we also use a time stamp on each proxy to speed up the registration of +// overlap query results. +// - where possible, we compare bound indices instead of values to reduce +// cache misses (TODO_ERIN). +// - no broadphase is perfect and neither is this one: it is not great for huge +// worlds (use a multi-SAP instead), it is not great for large objects. + +/** +* @private +*/ +public class b2BroadPhase implements IBroadPhase +{ +//public: + public function b2BroadPhase(worldAABB:b2AABB){ + //b2Settings.b2Assert(worldAABB.IsValid()); + var i:int; + + m_pairManager.Initialize(this); + + m_worldAABB = worldAABB; + + m_proxyCount = 0; + + // bounds array + m_bounds = new Vector. >(); + for (i = 0; i < 2; i++){ + m_bounds[i] = new Vector.(); + } + + //b2Vec2 d = worldAABB.upperBound - worldAABB.lowerBound; + var dX:Number = worldAABB.upperBound.x - worldAABB.lowerBound.x;; + var dY:Number = worldAABB.upperBound.y - worldAABB.lowerBound.y; + + m_quantizationFactor.x = b2Settings.USHRT_MAX / dX; + m_quantizationFactor.y = b2Settings.USHRT_MAX / dY; + + m_timeStamp = 1; + m_queryResultCount = 0; + } + //~b2BroadPhase(); + + // Use this to see if your proxy is in range. If it is not in range, + // it should be destroyed. Otherwise you may get O(m^2) pairs, where m + // is the number of proxies that are out of range. + public function InRange(aabb:b2AABB):Boolean{ + //b2Vec2 d = b2Max(aabb.lowerBound - m_worldAABB.upperBound, m_worldAABB.lowerBound - aabb.upperBound); + var dX:Number; + var dY:Number; + var d2X:Number; + var d2Y:Number; + + dX = aabb.lowerBound.x; + dY = aabb.lowerBound.y; + dX -= m_worldAABB.upperBound.x; + dY -= m_worldAABB.upperBound.y; + + d2X = m_worldAABB.lowerBound.x; + d2Y = m_worldAABB.lowerBound.y; + d2X -= aabb.upperBound.x; + d2Y -= aabb.upperBound.y; + + dX = b2Math.Max(dX, d2X); + dY = b2Math.Max(dY, d2Y); + + return b2Math.Max(dX, dY) < 0.0; + } + + // Create and destroy proxies. These call Flush first. + public function CreateProxy(aabb:b2AABB, userData:*):*{ + var index:uint; + var proxy:b2Proxy; + var i:int; + var j:int; + + //b2Settings.b2Assert(m_proxyCount < b2_maxProxies); + //b2Settings.b2Assert(m_freeProxy != b2Pair.b2_nullProxy); + + if (!m_freeProxy) + { + // As all proxies are allocated, m_proxyCount == m_proxyPool.length + m_freeProxy = m_proxyPool[m_proxyCount] = new b2Proxy(); + m_freeProxy.next = null; + m_freeProxy.timeStamp = 0; + m_freeProxy.overlapCount = b2_invalid; + m_freeProxy.userData = null; + + for (i = 0; i < 2; i++) + { + j = m_proxyCount * 2; + m_bounds[i][j++] = new b2Bound(); + m_bounds[i][j] = new b2Bound(); + } + + } + + proxy = m_freeProxy; + m_freeProxy = proxy.next; + + proxy.overlapCount = 0; + proxy.userData = userData; + + var boundCount:uint = 2 * m_proxyCount; + + var lowerValues:Vector. = new Vector.(); + var upperValues:Vector. = new Vector.(); + ComputeBounds(lowerValues, upperValues, aabb); + + for (var axis:int = 0; axis < 2; ++axis) + { + var bounds:Vector. = m_bounds[axis]; + var lowerIndex:uint; + var upperIndex:uint; + var lowerIndexOut:Vector. = new Vector.(); + lowerIndexOut.push(lowerIndex); + var upperIndexOut:Vector. = new Vector.(); + upperIndexOut.push(upperIndex); + QueryAxis(lowerIndexOut, upperIndexOut, lowerValues[axis], upperValues[axis], bounds, boundCount, axis); + lowerIndex = lowerIndexOut[0]; + upperIndex = upperIndexOut[0]; + + bounds.splice(upperIndex, 0, bounds[bounds.length - 1]); + bounds.length--; + bounds.splice(lowerIndex, 0, bounds[bounds.length - 1]); + bounds.length--; + + // The upper index has increased because of the lower bound insertion. + ++upperIndex; + + // Copy in the new bounds. + var tBound1:b2Bound = bounds[lowerIndex]; + var tBound2:b2Bound = bounds[upperIndex]; + tBound1.value = lowerValues[axis]; + tBound1.proxy = proxy; + tBound2.value = upperValues[axis]; + tBound2.proxy = proxy; + + var tBoundAS3:b2Bound = bounds[int(lowerIndex-1)]; + tBound1.stabbingCount = lowerIndex == 0 ? 0 : tBoundAS3.stabbingCount; + tBoundAS3 = bounds[int(upperIndex-1)]; + tBound2.stabbingCount = tBoundAS3.stabbingCount; + + // Adjust the stabbing count between the new bounds. + for (index = lowerIndex; index < upperIndex; ++index) + { + tBoundAS3 = bounds[index]; + tBoundAS3.stabbingCount++; + } + + // Adjust the all the affected bound indices. + for (index = lowerIndex; index < boundCount + 2; ++index) + { + tBound1 = bounds[index]; + var proxy2:b2Proxy = tBound1.proxy; + if (tBound1.IsLower()) + { + proxy2.lowerBounds[axis] = index; + } + else + { + proxy2.upperBounds[axis] = index; + } + } + } + + ++m_proxyCount; + + //b2Settings.b2Assert(m_queryResultCount < b2Settings.b2_maxProxies); + + for (i = 0; i < m_queryResultCount; ++i) + { + //b2Settings.b2Assert(m_queryResults[i] < b2_maxProxies); + //b2Settings.b2Assert(m_proxyPool[m_queryResults[i]].IsValid()); + + m_pairManager.AddBufferedPair(proxy, m_queryResults[i]); + } + + // Prepare for next query. + m_queryResultCount = 0; + IncrementTimeStamp(); + + return proxy; + } + + public function DestroyProxy(proxy_:*) : void { + var proxy:b2Proxy = proxy_ as b2Proxy; + var tBound1:b2Bound; + var tBound2:b2Bound; + + //b2Settings.b2Assert(proxy.IsValid()); + + var boundCount:int = 2 * m_proxyCount; + + for (var axis:int = 0; axis < 2; ++axis) + { + var bounds:Vector. = m_bounds[axis]; + + var lowerIndex:uint = proxy.lowerBounds[axis]; + var upperIndex:uint = proxy.upperBounds[axis]; + tBound1 = bounds[lowerIndex]; + var lowerValue:uint = tBound1.value; + tBound2 = bounds[upperIndex]; + var upperValue:uint = tBound2.value; + + bounds.splice(upperIndex, 1); + bounds.splice(lowerIndex, 1); + bounds.push(tBound1); + bounds.push(tBound2); + + + // Fix bound indices. + var tEnd:int = boundCount - 2; + for (var index:uint = lowerIndex; index < tEnd; ++index) + { + tBound1 = bounds[index]; + var proxy2:b2Proxy = tBound1.proxy; + if (tBound1.IsLower()) + { + proxy2.lowerBounds[axis] = index; + } + else + { + proxy2.upperBounds[axis] = index; + } + } + + // Fix stabbing count. + tEnd = upperIndex - 1; + for (var index2:int = lowerIndex; index2 < tEnd; ++index2) + { + tBound1 = bounds[index2]; + tBound1.stabbingCount--; + } + + // Query for pairs to be removed. lowerIndex and upperIndex are not needed. + // make lowerIndex and upper output using an array and do this for others if compiler doesn't pick them up + var ignore:Vector. = new Vector.(); + QueryAxis(ignore, ignore, lowerValue, upperValue, bounds, boundCount - 2, axis); + } + + //b2Settings.b2Assert(m_queryResultCount < b2Settings.b2_maxProxies); + + for (var i:int = 0; i < m_queryResultCount; ++i) + { + //b2Settings.b2Assert(m_proxyPool[m_queryResults[i]].IsValid()); + + m_pairManager.RemoveBufferedPair(proxy, m_queryResults[i]); + } + + // Prepare for next query. + m_queryResultCount = 0; + IncrementTimeStamp(); + + // Return the proxy to the pool. + proxy.userData = null; + proxy.overlapCount = b2_invalid; + proxy.lowerBounds[0] = b2_invalid; + proxy.lowerBounds[1] = b2_invalid; + proxy.upperBounds[0] = b2_invalid; + proxy.upperBounds[1] = b2_invalid; + + proxy.next = m_freeProxy; + m_freeProxy = proxy; + --m_proxyCount; + } + + + // Call MoveProxy as many times as you like, then when you are done + // call Commit to finalized the proxy pairs (for your time step). + public function MoveProxy(proxy_:*, aabb:b2AABB, displacement:b2Vec2) : void { + var proxy:b2Proxy = proxy_ as b2Proxy; + + var as3arr:Vector.; + var as3int:int; + + var axis:uint; + var index:uint; + var bound:b2Bound; + var prevBound:b2Bound; + var nextBound:b2Bound; + var nextProxyId:uint; + var nextProxy:b2Proxy; + + if (proxy == null) + { + //b2Settings.b2Assert(false); + return; + } + + if (aabb.IsValid() == false) + { + //b2Settings.b2Assert(false); + return; + } + + var boundCount:uint = 2 * m_proxyCount; + + // Get new bound values + var newValues:b2BoundValues = new b2BoundValues(); + ComputeBounds(newValues.lowerValues, newValues.upperValues, aabb); + + // Get old bound values + var oldValues:b2BoundValues = new b2BoundValues(); + for (axis = 0; axis < 2; ++axis) + { + bound = m_bounds[axis][proxy.lowerBounds[axis]]; + oldValues.lowerValues[axis] = bound.value; + bound = m_bounds[axis][proxy.upperBounds[axis]]; + oldValues.upperValues[axis] = bound.value; + } + + for (axis = 0; axis < 2; ++axis) + { + var bounds:Vector. = m_bounds[axis]; + + var lowerIndex:uint = proxy.lowerBounds[axis]; + var upperIndex:uint = proxy.upperBounds[axis]; + + var lowerValue:uint = newValues.lowerValues[axis]; + var upperValue:uint = newValues.upperValues[axis]; + + bound = bounds[lowerIndex]; + var deltaLower:int = lowerValue - bound.value; + bound.value = lowerValue; + + bound = bounds[upperIndex]; + var deltaUpper:int = upperValue - bound.value; + bound.value = upperValue; + + // + // Expanding adds overlaps + // + + // Should we move the lower bound down? + if (deltaLower < 0) + { + index = lowerIndex; + while (index > 0 && lowerValue < (bounds[int(index-1)] as b2Bound).value) + { + bound = bounds[index]; + prevBound = bounds[int(index - 1)]; + + var prevProxy:b2Proxy = prevBound.proxy; + + prevBound.stabbingCount++; + + if (prevBound.IsUpper() == true) + { + if (TestOverlapBound(newValues, prevProxy)) + { + m_pairManager.AddBufferedPair(proxy, prevProxy); + } + + //prevProxy.upperBounds[axis]++; + as3arr = prevProxy.upperBounds; + as3int = as3arr[axis]; + as3int++; + as3arr[axis] = as3int; + + bound.stabbingCount++; + } + else + { + //prevProxy.lowerBounds[axis]++; + as3arr = prevProxy.lowerBounds; + as3int = as3arr[axis]; + as3int++; + as3arr[axis] = as3int; + + bound.stabbingCount--; + } + + //proxy.lowerBounds[axis]--; + as3arr = proxy.lowerBounds; + as3int = as3arr[axis]; + as3int--; + as3arr[axis] = as3int; + + // swap + //var temp:b2Bound = bound; + //bound = prevEdge; + //prevEdge = temp; + bound.Swap(prevBound); + //b2Math.Swap(bound, prevEdge); + --index; + } + } + + // Should we move the upper bound up? + if (deltaUpper > 0) + { + index = upperIndex; + while (index < boundCount-1 && (bounds[int(index+1)] as b2Bound).value <= upperValue) + { + bound = bounds[ index ]; + nextBound = bounds[ int(index + 1) ]; + nextProxy = nextBound.proxy; + + nextBound.stabbingCount++; + + if (nextBound.IsLower() == true) + { + if (TestOverlapBound(newValues, nextProxy)) + { + m_pairManager.AddBufferedPair(proxy, nextProxy); + } + + //nextProxy.lowerBounds[axis]--; + as3arr = nextProxy.lowerBounds; + as3int = as3arr[axis]; + as3int--; + as3arr[axis] = as3int; + + bound.stabbingCount++; + } + else + { + //nextProxy.upperBounds[axis]--; + as3arr = nextProxy.upperBounds; + as3int = as3arr[axis]; + as3int--; + as3arr[axis] = as3int; + + bound.stabbingCount--; + } + + //proxy.upperBounds[axis]++; + as3arr = proxy.upperBounds; + as3int = as3arr[axis]; + as3int++; + as3arr[axis] = as3int; + + // swap + //var temp:b2Bound = bound; + //bound = nextEdge; + //nextEdge = temp; + bound.Swap(nextBound); + //b2Math.Swap(bound, nextEdge); + index++; + } + } + + // + // Shrinking removes overlaps + // + + // Should we move the lower bound up? + if (deltaLower > 0) + { + index = lowerIndex; + while (index < boundCount-1 && (bounds[int(index+1)] as b2Bound).value <= lowerValue) + { + bound = bounds[ index ]; + nextBound = bounds[ int(index + 1) ]; + + nextProxy = nextBound.proxy; + + nextBound.stabbingCount--; + + if (nextBound.IsUpper()) + { + if (TestOverlapBound(oldValues, nextProxy)) + { + m_pairManager.RemoveBufferedPair(proxy, nextProxy); + } + + //nextProxy.upperBounds[axis]--; + as3arr = nextProxy.upperBounds; + as3int = as3arr[axis]; + as3int--; + as3arr[axis] = as3int; + + bound.stabbingCount--; + } + else + { + //nextProxy.lowerBounds[axis]--; + as3arr = nextProxy.lowerBounds; + as3int = as3arr[axis]; + as3int--; + as3arr[axis] = as3int; + + bound.stabbingCount++; + } + + //proxy.lowerBounds[axis]++; + as3arr = proxy.lowerBounds; + as3int = as3arr[axis]; + as3int++; + as3arr[axis] = as3int; + + // swap + //var temp:b2Bound = bound; + //bound = nextEdge; + //nextEdge = temp; + bound.Swap(nextBound); + //b2Math.Swap(bound, nextEdge); + index++; + } + } + + // Should we move the upper bound down? + if (deltaUpper < 0) + { + index = upperIndex; + while (index > 0 && upperValue < (bounds[int(index-1)] as b2Bound).value) + { + bound = bounds[index]; + prevBound = bounds[int(index - 1)]; + + prevProxy = prevBound.proxy; + + prevBound.stabbingCount--; + + if (prevBound.IsLower() == true) + { + if (TestOverlapBound(oldValues, prevProxy)) + { + m_pairManager.RemoveBufferedPair(proxy, prevProxy); + } + + //prevProxy.lowerBounds[axis]++; + as3arr = prevProxy.lowerBounds; + as3int = as3arr[axis]; + as3int++; + as3arr[axis] = as3int; + + bound.stabbingCount--; + } + else + { + //prevProxy.upperBounds[axis]++; + as3arr = prevProxy.upperBounds; + as3int = as3arr[axis]; + as3int++; + as3arr[axis] = as3int; + + bound.stabbingCount++; + } + + //proxy.upperBounds[axis]--; + as3arr = proxy.upperBounds; + as3int = as3arr[axis]; + as3int--; + as3arr[axis] = as3int; + + // swap + //var temp:b2Bound = bound; + //bound = prevEdge; + //prevEdge = temp; + bound.Swap(prevBound); + //b2Math.Swap(bound, prevEdge); + index--; + } + } + } + } + + public function UpdatePairs(callback:Function) : void{ + m_pairManager.Commit(callback); + } + + public function TestOverlap(proxyA:*, proxyB:*):Boolean + { + var proxyA_:b2Proxy = proxyA as b2Proxy; + var proxyB_:b2Proxy = proxyB as b2Proxy; + if ( proxyA_.lowerBounds[0] > proxyB_.upperBounds[0]) return false; + if ( proxyB_.lowerBounds[0] > proxyA_.upperBounds[0]) return false; + if ( proxyA_.lowerBounds[1] > proxyB_.upperBounds[1]) return false; + if ( proxyB_.lowerBounds[1] > proxyA_.upperBounds[1]) return false; + return true; + } + + /** + * Get user data from a proxy. Returns null if the proxy is invalid. + */ + public function GetUserData(proxy:*):* + { + return (proxy as b2Proxy).userData; + } + + /** + * Get the AABB for a proxy. + */ + public function GetFatAABB(proxy_:*):b2AABB + { + var aabb:b2AABB = new b2AABB(); + var proxy:b2Proxy = proxy_ as b2Proxy; + aabb.lowerBound.x = m_worldAABB.lowerBound.x + m_bounds[0][proxy.lowerBounds[0]].value / m_quantizationFactor.x; + aabb.lowerBound.y = m_worldAABB.lowerBound.y + m_bounds[1][proxy.lowerBounds[1]].value / m_quantizationFactor.y; + aabb.upperBound.x = m_worldAABB.lowerBound.x + m_bounds[0][proxy.upperBounds[0]].value / m_quantizationFactor.x; + aabb.upperBound.y = m_worldAABB.lowerBound.y + m_bounds[1][proxy.upperBounds[1]].value / m_quantizationFactor.y; + return aabb; + } + + /** + * Get the number of proxies. + */ + public function GetProxyCount():int + { + return m_proxyCount; + } + + + /** + * Query an AABB for overlapping proxies. The callback class + * is called for each proxy that overlaps the supplied AABB. + */ + public function Query(callback:Function, aabb:b2AABB):void + { + var lowerValues:Vector. = new Vector.(); + var upperValues:Vector. = new Vector.(); + ComputeBounds(lowerValues, upperValues, aabb); + + var lowerIndex:uint; + var upperIndex:uint; + var lowerIndexOut:Vector. = new Vector.(); + lowerIndexOut.push(lowerIndex); + var upperIndexOut:Vector. = new Vector.(); + upperIndexOut.push(upperIndex); + QueryAxis(lowerIndexOut, upperIndexOut, lowerValues[0], upperValues[0], m_bounds[0], 2*m_proxyCount, 0); + QueryAxis(lowerIndexOut, upperIndexOut, lowerValues[1], upperValues[1], m_bounds[1], 2*m_proxyCount, 1); + + //b2Settings.b2Assert(m_queryResultCount < b2Settings.b2_maxProxies); + + // TODO: Don't be lazy, transform QueryAxis to directly call callback + for (var i:int = 0; i < m_queryResultCount; ++i) + { + var proxy:b2Proxy = m_queryResults[i]; + //b2Settings.b2Assert(proxy.IsValid()); + if (!callback(proxy)) + { + break; + } + } + + // Prepare for next query. + m_queryResultCount = 0; + IncrementTimeStamp(); + } + + public function Validate() : void{ + var pair:b2Pair; + var proxy1:b2Proxy; + var proxy2:b2Proxy; + var overlap:Boolean; + + for (var axis:int = 0; axis < 2; ++axis) + { + var bounds:Vector. = m_bounds[axis]; + + var boundCount:uint = 2 * m_proxyCount; + var stabbingCount:uint = 0; + + for (var i:uint = 0; i < boundCount; ++i) + { + var bound:b2Bound = bounds[i]; + //b2Settings.b2Assert(i == 0 || bounds[i-1].value <= bound->value); + //b2Settings.b2Assert(bound->proxyId != b2_nullProxy); + //b2Settings.b2Assert(m_proxyPool[bound->proxyId].IsValid()); + + if (bound.IsLower() == true) + { + //b2Settings.b2Assert(m_proxyPool[bound.proxyId].lowerBounds[axis] == i); + stabbingCount++; + } + else + { + //b2Settings.b2Assert(m_proxyPool[bound.proxyId].upperBounds[axis] == i); + stabbingCount--; + } + + //b2Settings.b2Assert(bound.stabbingCount == stabbingCount); + } + } + + } + + public function Rebalance(iterations:int):void + { + // Do nothing + } + + + /** + * @inheritDoc + */ + public function RayCast(callback:Function, input:b2RayCastInput):void + { + var subInput:b2RayCastInput = new b2RayCastInput(); + subInput.p1.SetV(input.p1); + subInput.p2.SetV(input.p2); + subInput.maxFraction = input.maxFraction; + + + var dx:Number = (input.p2.x-input.p1.x)*m_quantizationFactor.x; + var dy:Number = (input.p2.y-input.p1.y)*m_quantizationFactor.y; + + var sx:int = dx<-Number.MIN_VALUE ? -1 : (dx>Number.MIN_VALUE ? 1 : 0); + var sy:int = dy<-Number.MIN_VALUE ? -1 : (dy>Number.MIN_VALUE ? 1 : 0); + + //b2Settings.b2Assert(sx!=0||sy!=0); + + var p1x:Number = m_quantizationFactor.x * (input.p1.x - m_worldAABB.lowerBound.x); + var p1y:Number = m_quantizationFactor.y * (input.p1.y - m_worldAABB.lowerBound.y); + + var startValues:Array = new Array(); + var startValues2:Array = new Array(); + startValues[0]=uint(p1x) & (b2Settings.USHRT_MAX - 1); + startValues[1]=uint(p1y) & (b2Settings.USHRT_MAX - 1); + startValues2[0]=startValues[0]+1; + startValues2[1]=startValues[1]+1; + + var startIndices:Array = new Array(); + + var xIndex:int; + var yIndex:int; + + var proxy:b2Proxy; + + + //First deal with all the proxies that contain segment.p1 + var lowerIndex:uint; + var upperIndex:uint; + var lowerIndexOut:Vector. = new Vector.(); + lowerIndexOut.push(lowerIndex); + var upperIndexOut:Vector. = new Vector.(); + upperIndexOut.push(upperIndex); + QueryAxis(lowerIndexOut, upperIndexOut, startValues[0], startValues2[0], m_bounds[0], 2*m_proxyCount, 0); + if(sx>=0) xIndex = upperIndexOut[0]-1; + else xIndex = lowerIndexOut[0]; + QueryAxis(lowerIndexOut, upperIndexOut, startValues[1], startValues2[1], m_bounds[1], 2*m_proxyCount, 1); + if(sy>=0) yIndex = upperIndexOut[0]-1; + else yIndex = lowerIndexOut[0]; + + // Callback for starting proxies: + for (var i:int = 0; i < m_queryResultCount; i++) { + subInput.maxFraction = callback(m_queryResults[i], subInput); + } + + //Now work through the rest of the segment + for (;; ) + { + var xProgress:Number = 0; + var yProgress:Number = 0; + //Move on to next bound + xIndex += sx >= 0?1: -1; + if(xIndex<0||xIndex>=m_proxyCount*2) + break; + if(sx!=0){ + xProgress = (m_bounds[0][xIndex].value - p1x) / dx; + } + //Move on to next bound + yIndex += sy >= 0?1: -1; + if(yIndex<0||yIndex>=m_proxyCount*2) + break; + if(sy!=0){ + yProgress = (m_bounds[1][yIndex].value - p1y) / dy; + } + for (;; ) + { + if(sy==0||(sx!=0&&xProgresssubInput.maxFraction) + break; + + //Check that we are entering a proxy, not leaving + if(sx>0?m_bounds[0][xIndex].IsLower():m_bounds[0][xIndex].IsUpper()){ + //Check the other axis of the proxy + proxy = m_bounds[0][xIndex].proxy; + if(sy>=0){ + if(proxy.lowerBounds[1]<=yIndex-1&&proxy.upperBounds[1]>=yIndex){ + //Add the proxy + subInput.maxFraction = callback(proxy, subInput); + } + }else{ + if(proxy.lowerBounds[1]<=yIndex&&proxy.upperBounds[1]>=yIndex+1){ + //Add the proxy + subInput.maxFraction = callback(proxy, subInput); + } + } + } + + //Early out + if(subInput.maxFraction==0) + break; + + //Move on to the next bound + if(sx>0){ + xIndex++; + if(xIndex==m_proxyCount*2) + break; + }else{ + xIndex--; + if(xIndex<0) + break; + } + xProgress = (m_bounds[0][xIndex].value - p1x) / dx; + }else{ + if(yProgress>subInput.maxFraction) + break; + + //Check that we are entering a proxy, not leaving + if(sy>0?m_bounds[1][yIndex].IsLower():m_bounds[1][yIndex].IsUpper()){ + //Check the other axis of the proxy + proxy = m_bounds[1][yIndex].proxy; + if(sx>=0){ + if(proxy.lowerBounds[0]<=xIndex-1&&proxy.upperBounds[0]>=xIndex){ + //Add the proxy + subInput.maxFraction = callback(proxy, subInput); + } + }else{ + if(proxy.lowerBounds[0]<=xIndex&&proxy.upperBounds[0]>=xIndex+1){ + //Add the proxy + subInput.maxFraction = callback(proxy, subInput); + } + } + } + + //Early out + if(subInput.maxFraction==0) + break; + + //Move on to the next bound + if(sy>0){ + yIndex++; + if(yIndex==m_proxyCount*2) + break; + }else{ + yIndex--; + if(yIndex<0) + break; + } + yProgress = (m_bounds[1][yIndex].value - p1y) / dy; + } + } + break; + } + + // Prepare for next query. + m_queryResultCount = 0; + IncrementTimeStamp(); + + return; + } + +//private: + private function ComputeBounds(lowerValues:Vector., upperValues:Vector., aabb:b2AABB) : void + { + //b2Settings.b2Assert(aabb.upperBound.x >= aabb.lowerBound.x); + //b2Settings.b2Assert(aabb.upperBound.y >= aabb.lowerBound.y); + + //var minVertex:b2Vec2 = b2Math.ClampV(aabb.minVertex, m_worldAABB.minVertex, m_worldAABB.maxVertex); + var minVertexX:Number = aabb.lowerBound.x; + var minVertexY:Number = aabb.lowerBound.y; + minVertexX = b2Math.Min(minVertexX, m_worldAABB.upperBound.x); + minVertexY = b2Math.Min(minVertexY, m_worldAABB.upperBound.y); + minVertexX = b2Math.Max(minVertexX, m_worldAABB.lowerBound.x); + minVertexY = b2Math.Max(minVertexY, m_worldAABB.lowerBound.y); + + //var maxVertex:b2Vec2 = b2Math.ClampV(aabb.maxVertex, m_worldAABB.minVertex, m_worldAABB.maxVertex); + var maxVertexX:Number = aabb.upperBound.x; + var maxVertexY:Number = aabb.upperBound.y; + maxVertexX = b2Math.Min(maxVertexX, m_worldAABB.upperBound.x); + maxVertexY = b2Math.Min(maxVertexY, m_worldAABB.upperBound.y); + maxVertexX = b2Math.Max(maxVertexX, m_worldAABB.lowerBound.x); + maxVertexY = b2Math.Max(maxVertexY, m_worldAABB.lowerBound.y); + + // Bump lower bounds downs and upper bounds up. This ensures correct sorting of + // lower/upper bounds that would have equal values. + // TODO_ERIN implement fast float to uint16 conversion. + lowerValues[0] = uint(m_quantizationFactor.x * (minVertexX - m_worldAABB.lowerBound.x)) & (b2Settings.USHRT_MAX - 1); + upperValues[0] = (uint(m_quantizationFactor.x * (maxVertexX - m_worldAABB.lowerBound.x))& 0x0000ffff) | 1; + + lowerValues[1] = uint(m_quantizationFactor.y * (minVertexY - m_worldAABB.lowerBound.y)) & (b2Settings.USHRT_MAX - 1); + upperValues[1] = (uint(m_quantizationFactor.y * (maxVertexY - m_worldAABB.lowerBound.y))& 0x0000ffff) | 1; + } + + // This one is only used for validation. + private function TestOverlapValidate(p1:b2Proxy, p2:b2Proxy):Boolean{ + + for (var axis:int = 0; axis < 2; ++axis) + { + var bounds:Vector. = m_bounds[axis]; + + //b2Settings.b2Assert(p1.lowerBounds[axis] < 2 * m_proxyCount); + //b2Settings.b2Assert(p1.upperBounds[axis] < 2 * m_proxyCount); + //b2Settings.b2Assert(p2.lowerBounds[axis] < 2 * m_proxyCount); + //b2Settings.b2Assert(p2.upperBounds[axis] < 2 * m_proxyCount); + + var bound1:b2Bound = bounds[p1.lowerBounds[axis]]; + var bound2:b2Bound = bounds[p2.upperBounds[axis]]; + if (bound1.value > bound2.value) + return false; + + bound1 = bounds[p1.upperBounds[axis]]; + bound2 = bounds[p2.lowerBounds[axis]]; + if (bound1.value < bound2.value) + return false; + } + + return true; + } + + public function TestOverlapBound(b:b2BoundValues, p:b2Proxy):Boolean + { + for (var axis:int = 0; axis < 2; ++axis) + { + var bounds:Vector. = m_bounds[axis]; + + //b2Settings.b2Assert(p.lowerBounds[axis] < 2 * m_proxyCount); + //b2Settings.b2Assert(p.upperBounds[axis] < 2 * m_proxyCount); + + var bound:b2Bound = bounds[p.upperBounds[axis]]; + if (b.lowerValues[axis] > bound.value) + return false; + + bound = bounds[p.lowerBounds[axis]]; + if (b.upperValues[axis] < bound.value) + return false; + } + + return true; + } + + private function QueryAxis(lowerQueryOut:Vector., upperQueryOut:Vector., lowerValue:uint, upperValue:uint, bounds:Vector., boundCount:uint, axis:int) : void{ + + var lowerQuery:uint = BinarySearch(bounds, boundCount, lowerValue); + var upperQuery:uint = BinarySearch(bounds, boundCount, upperValue); + var bound: b2Bound; + + // Easy case: lowerQuery <= lowerIndex(i) < upperQuery + // Solution: search query range for min bounds. + for (var j:uint = lowerQuery; j < upperQuery; ++j) + { + bound = bounds[j]; + if (bound.IsLower()) + { + IncrementOverlapCount(bound.proxy); + } + } + + // Hard case: lowerIndex(i) < lowerQuery < upperIndex(i) + // Solution: use the stabbing count to search down the bound array. + if (lowerQuery > 0) + { + var i:int = lowerQuery - 1; + bound = bounds[i]; + var s:int = bound.stabbingCount; + + // Find the s overlaps. + while (s) + { + //b2Settings.b2Assert(i >= 0); + bound = bounds[i]; + if (bound.IsLower()) + { + var proxy:b2Proxy = bound.proxy; + if (lowerQuery <= proxy.upperBounds[axis]) + { + IncrementOverlapCount(bound.proxy); + --s; + } + } + --i; + } + } + + lowerQueryOut[0] = lowerQuery; + upperQueryOut[0] = upperQuery; + } + + private function IncrementOverlapCount(proxy:b2Proxy) : void{ + if (proxy.timeStamp < m_timeStamp) + { + proxy.timeStamp = m_timeStamp; + proxy.overlapCount = 1; + } + else + { + proxy.overlapCount = 2; + //b2Settings.b2Assert(m_queryResultCount < b2Settings.b2_maxProxies); + m_queryResults[m_queryResultCount] = proxy; + ++m_queryResultCount; + } + } + private function IncrementTimeStamp() : void{ + if (m_timeStamp == b2Settings.USHRT_MAX) + { + for (var i:uint = 0; i < m_proxyPool.length; ++i) + { + (m_proxyPool[i] as b2Proxy).timeStamp = 0; + } + m_timeStamp = 1; + } + else + { + ++m_timeStamp; + } + } + + b2internal var m_pairManager:b2PairManager = new b2PairManager(); + + b2internal var m_proxyPool:Array = new Array(); + private var m_freeProxy:b2Proxy; + + b2internal var m_bounds:Vector. > ; + + private var m_querySortKeys:Array = new Array(); + private var m_queryResults:Array = new Array(); + private var m_queryResultCount:int; + + b2internal var m_worldAABB:b2AABB; + b2internal var m_quantizationFactor:b2Vec2 = new b2Vec2(); + b2internal var m_proxyCount:int; + private var m_timeStamp:uint; + + static public var s_validate:Boolean = false; + + static public const b2_invalid:uint = b2Settings.USHRT_MAX; + static public const b2_nullEdge:uint = b2Settings.USHRT_MAX; + + + static public function BinarySearch(bounds:Vector., count:int, value:uint):uint + { + var low:int = 0; + var high:int = count - 1; + while (low <= high) + { + var mid:int = ((low + high) / 2); + var bound:b2Bound = bounds[mid]; + if (bound.value > value) + { + high = mid - 1; + } + else if (bound.value < value) + { + low = mid + 1; + } + else + { + return uint(mid); + } + } + + return uint(low); + } + + +}; +} diff --git a/srclib/Box2D/Collision/b2Collision.as b/srclib/Box2D/Collision/b2Collision.as new file mode 100644 index 00000000..5123b642 --- /dev/null +++ b/srclib/Box2D/Collision/b2Collision.as @@ -0,0 +1,701 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision{ + + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; +use namespace b2internal; + + +/** +* @private +*/ +public class b2Collision{ + + // Null feature + static public const b2_nullFeature:uint = 0x000000ff;//UCHAR_MAX; + + // Sutherland-Hodgman clipping. + static public function ClipSegmentToLine(vOut:Vector., vIn:Vector., normal:b2Vec2, offset:Number):int + { + var cv:ClipVertex; + + // Start with no output points + var numOut:int = 0; + + cv = vIn[0]; + var vIn0:b2Vec2 = cv.v; + cv = vIn[1]; + var vIn1:b2Vec2 = cv.v; + + // Calculate the distance of end points to the line + var distance0:Number = normal.x * vIn0.x + normal.y * vIn0.y - offset; + var distance1:Number = normal.x * vIn1.x + normal.y * vIn1.y - offset; + + // If the points are behind the plane + if (distance0 <= 0.0) vOut[numOut++].Set(vIn[0]); + if (distance1 <= 0.0) vOut[numOut++].Set(vIn[1]); + + // If the points are on different sides of the plane + if (distance0 * distance1 < 0.0) + { + // Find intersection point of edge and plane + var interp:Number = distance0 / (distance0 - distance1); + // expanded for performance + // vOut[numOut].v = vIn[0].v + interp * (vIn[1].v - vIn[0].v); + cv = vOut[numOut]; + var tVec:b2Vec2 = cv.v; + tVec.x = vIn0.x + interp * (vIn1.x - vIn0.x); + tVec.y = vIn0.y + interp * (vIn1.y - vIn0.y); + cv = vOut[numOut]; + var cv2: ClipVertex; + if (distance0 > 0.0) + { + cv2 = vIn[0]; + cv.id = cv2.id; + } + else + { + cv2 = vIn[1]; + cv.id = cv2.id; + } + ++numOut; + } + + return numOut; + } + + + // Find the separation between poly1 and poly2 for a give edge normal on poly1. + static public function EdgeSeparation( poly1:b2PolygonShape, xf1:b2Transform, edge1:int, + poly2:b2PolygonShape, xf2:b2Transform):Number + { + var count1:int = poly1.m_vertexCount; + var vertices1:Vector. = poly1.m_vertices; + var normals1:Vector. = poly1.m_normals; + + var count2:int = poly2.m_vertexCount; + var vertices2:Vector. = poly2.m_vertices; + + //b2Assert(0 <= edge1 && edge1 < count1); + + var tMat:b2Mat22; + var tVec:b2Vec2; + + // Convert normal from poly1's frame into poly2's frame. + //b2Vec2 normal1World = b2Mul(xf1.R, normals1[edge1]); + tMat = xf1.R; + tVec = normals1[edge1]; + var normal1WorldX:Number = (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var normal1WorldY:Number = (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //b2Vec2 normal1 = b2MulT(xf2.R, normal1World); + tMat = xf2.R; + var normal1X:Number = (tMat.col1.x * normal1WorldX + tMat.col1.y * normal1WorldY); + var normal1Y:Number = (tMat.col2.x * normal1WorldX + tMat.col2.y * normal1WorldY); + + // Find support vertex on poly2 for -normal. + var index:int = 0; + var minDot:Number = Number.MAX_VALUE; + for (var i:int = 0; i < count2; ++i) + { + //float32 dot = b2Dot(poly2->m_vertices[i], normal1); + tVec = vertices2[i]; + var dot:Number = tVec.x * normal1X + tVec.y * normal1Y; + if (dot < minDot) + { + minDot = dot; + index = i; + } + } + + //b2Vec2 v1 = b2Mul(xf1, vertices1[edge1]); + tVec = vertices1[edge1]; + tMat = xf1.R; + var v1X:Number = xf1.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var v1Y:Number = xf1.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //b2Vec2 v2 = b2Mul(xf2, vertices2[index]); + tVec = vertices2[index]; + tMat = xf2.R; + var v2X:Number = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var v2Y:Number = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + //var separation:Number = b2Math.b2Dot( b2Math.SubtractVV( v2, v1 ) , normal); + v2X -= v1X; + v2Y -= v1Y; + //float32 separation = b2Dot(v2 - v1, normal1World); + var separation:Number = v2X * normal1WorldX + v2Y * normal1WorldY; + return separation; + } + + + + + // Find the max separation between poly1 and poly2 using edge normals + // from poly1. + static public function FindMaxSeparation(edgeIndex:Vector., + poly1:b2PolygonShape, xf1:b2Transform, + poly2:b2PolygonShape, xf2:b2Transform):Number + { + var count1:int = poly1.m_vertexCount; + var normals1:Vector. = poly1.m_normals; + + var tVec:b2Vec2; + var tMat:b2Mat22; + + // Vector pointing from the centroid of poly1 to the centroid of poly2. + //b2Vec2 d = b2Mul(xf2, poly2->m_centroid) - b2Mul(xf1, poly1->m_centroid); + tMat = xf2.R; + tVec = poly2.m_centroid; + var dX:Number = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var dY:Number = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + tMat = xf1.R; + tVec = poly1.m_centroid; + dX -= xf1.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + dY -= xf1.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + //b2Vec2 dLocal1 = b2MulT(xf1.R, d); + var dLocal1X:Number = (dX * xf1.R.col1.x + dY * xf1.R.col1.y); + var dLocal1Y:Number = (dX * xf1.R.col2.x + dY * xf1.R.col2.y); + + // Get support vertex as a hint for our search + var edge:int = 0; + var maxDot:Number = -Number.MAX_VALUE; + for (var i:int = 0; i < count1; ++i) + { + //var dot:Number = b2Math.b2Dot(normals1[i], dLocal1); + tVec = normals1[i]; + var dot:Number = (tVec.x * dLocal1X + tVec.y * dLocal1Y); + if (dot > maxDot) + { + maxDot = dot; + edge = i; + } + } + + // Get the separation for the edge normal. + var s:Number = EdgeSeparation(poly1, xf1, edge, poly2, xf2); + + // Check the separation for the previous edge normal. + var prevEdge:int = edge - 1 >= 0 ? edge - 1 : count1 - 1; + var sPrev:Number = EdgeSeparation(poly1, xf1, prevEdge, poly2, xf2); + + // Check the separation for the next edge normal. + var nextEdge:int = edge + 1 < count1 ? edge + 1 : 0; + var sNext:Number = EdgeSeparation(poly1, xf1, nextEdge, poly2, xf2); + + // Find the best edge and the search direction. + var bestEdge:int; + var bestSeparation:Number; + var increment:int; + if (sPrev > s && sPrev > sNext) + { + increment = -1; + bestEdge = prevEdge; + bestSeparation = sPrev; + } + else if (sNext > s) + { + increment = 1; + bestEdge = nextEdge; + bestSeparation = sNext; + } + else + { + // pointer out + edgeIndex[0] = edge; + return s; + } + + // Perform a local search for the best edge normal. + while (true) + { + + if (increment == -1) + edge = bestEdge - 1 >= 0 ? bestEdge - 1 : count1 - 1; + else + edge = bestEdge + 1 < count1 ? bestEdge + 1 : 0; + + s = EdgeSeparation(poly1, xf1, edge, poly2, xf2); + + if (s > bestSeparation) + { + bestEdge = edge; + bestSeparation = s; + } + else + { + break; + } + } + + // pointer out + edgeIndex[0] = bestEdge; + return bestSeparation; + } + + + + static public function FindIncidentEdge(c:Vector., + poly1:b2PolygonShape, xf1:b2Transform, edge1:int, + poly2:b2PolygonShape, xf2:b2Transform) : void + { + var count1:int = poly1.m_vertexCount; + var normals1:Vector. = poly1.m_normals; + + var count2:int = poly2.m_vertexCount; + var vertices2:Vector. = poly2.m_vertices; + var normals2:Vector. = poly2.m_normals; + + //b2Assert(0 <= edge1 && edge1 < count1); + + var tMat:b2Mat22; + var tVec:b2Vec2; + + // Get the normal of the reference edge in poly2's frame. + //b2Vec2 normal1 = b2MulT(xf2.R, b2Mul(xf1.R, normals1[edge1])); + tMat = xf1.R; + tVec = normals1[edge1]; + var normal1X:Number = (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var normal1Y:Number = (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + tMat = xf2.R; + var tX:Number = (tMat.col1.x * normal1X + tMat.col1.y * normal1Y); + normal1Y = (tMat.col2.x * normal1X + tMat.col2.y * normal1Y); + normal1X = tX; + + // Find the incident edge on poly2. + var index:int = 0; + var minDot:Number = Number.MAX_VALUE; + for (var i:int = 0; i < count2; ++i) + { + //var dot:Number = b2Dot(normal1, normals2[i]); + tVec = normals2[i]; + var dot:Number = (normal1X * tVec.x + normal1Y * tVec.y); + if (dot < minDot) + { + minDot = dot; + index = i; + } + } + + var tClip:ClipVertex; + // Build the clip vertices for the incident edge. + var i1:int = index; + var i2:int = i1 + 1 < count2 ? i1 + 1 : 0; + + tClip = c[0]; + //c[0].v = b2Mul(xf2, vertices2[i1]); + tVec = vertices2[i1]; + tMat = xf2.R; + tClip.v.x = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + tClip.v.y = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + tClip.id.features.referenceEdge = edge1; + tClip.id.features.incidentEdge = i1; + tClip.id.features.incidentVertex = 0; + + tClip = c[1]; + //c[1].v = b2Mul(xf2, vertices2[i2]); + tVec = vertices2[i2]; + tMat = xf2.R; + tClip.v.x = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + tClip.v.y = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + tClip.id.features.referenceEdge = edge1; + tClip.id.features.incidentEdge = i2; + tClip.id.features.incidentVertex = 1; + } + + + private static function MakeClipPointVector():Vector. + { + var r:Vector. = new Vector.(2); + r[0] = new ClipVertex(); + r[1] = new ClipVertex(); + return r; + } + private static var s_incidentEdge:Vector. = MakeClipPointVector(); + private static var s_clipPoints1:Vector. = MakeClipPointVector(); + private static var s_clipPoints2:Vector. = MakeClipPointVector(); + private static var s_edgeAO:Vector. = new Vector.(1); + private static var s_edgeBO:Vector. = new Vector.(1); + private static var s_localTangent:b2Vec2 = new b2Vec2(); + private static var s_localNormal:b2Vec2 = new b2Vec2(); + private static var s_planePoint:b2Vec2 = new b2Vec2(); + private static var s_normal:b2Vec2 = new b2Vec2(); + private static var s_tangent:b2Vec2 = new b2Vec2(); + private static var s_tangent2:b2Vec2 = new b2Vec2(); + private static var s_v11:b2Vec2 = new b2Vec2(); + private static var s_v12:b2Vec2 = new b2Vec2(); + // Find edge normal of max separation on A - return if separating axis is found + // Find edge normal of max separation on B - return if separation axis is found + // Choose reference edge as min(minA, minB) + // Find incident edge + // Clip + static private var b2CollidePolyTempVec:b2Vec2 = new b2Vec2(); + // The normal points from 1 to 2 + static public function CollidePolygons(manifold:b2Manifold, + polyA:b2PolygonShape, xfA:b2Transform, + polyB:b2PolygonShape, xfB:b2Transform) : void + { + var cv: ClipVertex; + + manifold.m_pointCount = 0; + var totalRadius:Number = polyA.m_radius + polyB.m_radius; + + var edgeA:int = 0; + s_edgeAO[0] = edgeA; + var separationA:Number = FindMaxSeparation(s_edgeAO, polyA, xfA, polyB, xfB); + edgeA = s_edgeAO[0]; + if (separationA > totalRadius) + return; + + var edgeB:int = 0; + s_edgeBO[0] = edgeB; + var separationB:Number = FindMaxSeparation(s_edgeBO, polyB, xfB, polyA, xfA); + edgeB = s_edgeBO[0]; + if (separationB > totalRadius) + return; + + var poly1:b2PolygonShape; // reference poly + var poly2:b2PolygonShape; // incident poly + var xf1:b2Transform; + var xf2:b2Transform; + var edge1:int; // reference edge + var flip:uint; + const k_relativeTol:Number = 0.98; + const k_absoluteTol:Number = 0.001; + var tMat:b2Mat22; + + if (separationB > k_relativeTol * separationA + k_absoluteTol) + { + poly1 = polyB; + poly2 = polyA; + xf1 = xfB; + xf2 = xfA; + edge1 = edgeB; + manifold.m_type = b2Manifold.e_faceB; + flip = 1; + } + else + { + poly1 = polyA; + poly2 = polyB; + xf1 = xfA; + xf2 = xfB; + edge1 = edgeA; + manifold.m_type = b2Manifold.e_faceA; + flip = 0; + } + + var incidentEdge:Vector. = s_incidentEdge; + FindIncidentEdge(incidentEdge, poly1, xf1, edge1, poly2, xf2); + + var count1:int = poly1.m_vertexCount; + var vertices1:Vector. = poly1.m_vertices; + + var local_v11:b2Vec2 = vertices1[edge1]; + var local_v12:b2Vec2; + if (edge1 + 1 < count1) { + local_v12 = vertices1[int(edge1+1)]; + } else { + local_v12 = vertices1[0]; + } + + var localTangent:b2Vec2 = s_localTangent; + localTangent.Set(local_v12.x - local_v11.x, local_v12.y - local_v11.y); + localTangent.Normalize(); + + var localNormal:b2Vec2 = s_localNormal; + localNormal.x = localTangent.y; + localNormal.y = -localTangent.x; + + var planePoint:b2Vec2 = s_planePoint; + planePoint.Set(0.5 * (local_v11.x + local_v12.x), 0.5 * (local_v11.y + local_v12.y)); + + var tangent:b2Vec2 = s_tangent; + //tangent = b2Math.b2MulMV(xf1.R, localTangent); + tMat = xf1.R; + tangent.x = (tMat.col1.x * localTangent.x + tMat.col2.x * localTangent.y); + tangent.y = (tMat.col1.y * localTangent.x + tMat.col2.y * localTangent.y); + var tangent2:b2Vec2 = s_tangent2; + tangent2.x = - tangent.x; + tangent2.y = - tangent.y; + var normal:b2Vec2 = s_normal; + normal.x = tangent.y; + normal.y = -tangent.x; + + //v11 = b2Math.MulX(xf1, local_v11); + //v12 = b2Math.MulX(xf1, local_v12); + var v11:b2Vec2 = s_v11; + var v12:b2Vec2 = s_v12; + v11.x = xf1.position.x + (tMat.col1.x * local_v11.x + tMat.col2.x * local_v11.y); + v11.y = xf1.position.y + (tMat.col1.y * local_v11.x + tMat.col2.y * local_v11.y); + v12.x = xf1.position.x + (tMat.col1.x * local_v12.x + tMat.col2.x * local_v12.y); + v12.y = xf1.position.y + (tMat.col1.y * local_v12.x + tMat.col2.y * local_v12.y); + + // Face offset + var frontOffset:Number = normal.x * v11.x + normal.y * v11.y; + // Side offsets, extended by polytope skin thickness + var sideOffset1:Number = -tangent.x * v11.x - tangent.y * v11.y + totalRadius; + var sideOffset2:Number = tangent.x * v12.x + tangent.y * v12.y + totalRadius; + + // Clip incident edge against extruded edge1 side edges. + var clipPoints1:Vector. = s_clipPoints1; + var clipPoints2:Vector. = s_clipPoints2; + var np:int; + + // Clip to box side 1 + //np = ClipSegmentToLine(clipPoints1, incidentEdge, -tangent, sideOffset1); + np = ClipSegmentToLine(clipPoints1, incidentEdge, tangent2, sideOffset1); + + if (np < 2) + return; + + // Clip to negative box side 1 + np = ClipSegmentToLine(clipPoints2, clipPoints1, tangent, sideOffset2); + + if (np < 2) + return; + + // Now clipPoints2 contains the clipped points. + manifold.m_localPlaneNormal.SetV(localNormal); + manifold.m_localPoint.SetV(planePoint); + + var pointCount:int = 0; + for (var i:int = 0; i < b2Settings.b2_maxManifoldPoints;++i) + { + cv = clipPoints2[i]; + var separation:Number = normal.x * cv.v.x + normal.y * cv.v.y - frontOffset; + if (separation <= totalRadius) + { + var cp:b2ManifoldPoint = manifold.m_points[ pointCount ]; + //cp.m_localPoint = b2Math.b2MulXT(xf2, cv.v); + tMat = xf2.R; + var tX:Number = cv.v.x - xf2.position.x; + var tY:Number = cv.v.y - xf2.position.y; + cp.m_localPoint.x = (tX * tMat.col1.x + tY * tMat.col1.y ); + cp.m_localPoint.y = (tX * tMat.col2.x + tY * tMat.col2.y ); + cp.m_id.Set(cv.id); + cp.m_id.features.flip = flip; + ++pointCount; + } + } + + manifold.m_pointCount = pointCount; + } + + + + static public function CollideCircles( + manifold:b2Manifold, + circle1:b2CircleShape, xf1:b2Transform, + circle2:b2CircleShape, xf2:b2Transform) : void + { + manifold.m_pointCount = 0; + + var tMat:b2Mat22; + var tVec:b2Vec2; + + //b2Vec2 p1 = b2Mul(xf1, circle1->m_p); + tMat = xf1.R; tVec = circle1.m_p; + var p1X:Number = xf1.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var p1Y:Number = xf1.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //b2Vec2 p2 = b2Mul(xf2, circle2->m_p); + tMat = xf2.R; tVec = circle2.m_p; + var p2X:Number = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var p2Y:Number = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //b2Vec2 d = p2 - p1; + var dX:Number = p2X - p1X; + var dY:Number = p2Y - p1Y; + //var distSqr:Number = b2Math.b2Dot(d, d); + var distSqr:Number = dX * dX + dY * dY; + var radius:Number = circle1.m_radius + circle2.m_radius; + if (distSqr > radius * radius) + { + return; + } + manifold.m_type = b2Manifold.e_circles; + manifold.m_localPoint.SetV(circle1.m_p); + manifold.m_localPlaneNormal.SetZero(); + manifold.m_pointCount = 1; + manifold.m_points[0].m_localPoint.SetV(circle2.m_p); + manifold.m_points[0].m_id.key = 0; + } + + + + static public function CollidePolygonAndCircle( + manifold:b2Manifold, + polygon:b2PolygonShape, xf1:b2Transform, + circle:b2CircleShape, xf2:b2Transform) : void + { + manifold.m_pointCount = 0; + var tPoint:b2ManifoldPoint; + + var dX:Number; + var dY:Number; + var positionX:Number; + var positionY:Number; + + var tVec:b2Vec2; + var tMat:b2Mat22; + + // Compute circle position in the frame of the polygon. + //b2Vec2 c = b2Mul(xf2, circle->m_localPosition); + tMat = xf2.R; + tVec = circle.m_p; + var cX:Number = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var cY:Number = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + //b2Vec2 cLocal = b2MulT(xf1, c); + dX = cX - xf1.position.x; + dY = cY - xf1.position.y; + tMat = xf1.R; + var cLocalX:Number = (dX * tMat.col1.x + dY * tMat.col1.y); + var cLocalY:Number = (dX * tMat.col2.x + dY * tMat.col2.y); + + var dist:Number; + + // Find the min separating edge. + var normalIndex:int = 0; + var separation:Number = -Number.MAX_VALUE; + var radius:Number = polygon.m_radius + circle.m_radius; + var vertexCount:int = polygon.m_vertexCount; + var vertices:Vector. = polygon.m_vertices; + var normals:Vector. = polygon.m_normals; + + for (var i:int = 0; i < vertexCount; ++i) + { + //float32 s = b2Dot(normals[i], cLocal - vertices[i]); + tVec = vertices[i]; + dX = cLocalX-tVec.x; + dY = cLocalY-tVec.y; + tVec = normals[i]; + var s:Number = tVec.x * dX + tVec.y * dY; + + if (s > radius) + { + // Early out. + return; + } + + if (s > separation) + { + separation = s; + normalIndex = i; + } + } + // Vertices that subtend the incident face + var vertIndex1:int = normalIndex; + var vertIndex2:int = vertIndex1 + 1 < vertexCount?vertIndex1 + 1:0; + var v1:b2Vec2 = vertices[vertIndex1]; + var v2:b2Vec2 = vertices[vertIndex2]; + + // If the center is inside the polygon ... + if (separation < Number.MIN_VALUE) + { + manifold.m_pointCount = 1; + manifold.m_type = b2Manifold.e_faceA; + manifold.m_localPlaneNormal.SetV(normals[normalIndex]); + manifold.m_localPoint.x = 0.5 * (v1.x + v2.x); + manifold.m_localPoint.y = 0.5 * (v1.y + v2.y); + manifold.m_points[0].m_localPoint.SetV(circle.m_p); + manifold.m_points[0].m_id.key = 0; + return; + } + + // Project the circle center onto the edge segment. + var u1:Number = (cLocalX - v1.x) * (v2.x - v1.x) + (cLocalY - v1.y) * (v2.y - v1.y); + var u2:Number = (cLocalX - v2.x) * (v1.x - v2.x) + (cLocalY - v2.y) * (v1.y - v2.y); + if (u1 <= 0.0) + { + if ((cLocalX-v1.x)*(cLocalX-v1.x)+(cLocalY-v1.y)*(cLocalY-v1.y) > radius * radius) + return; + manifold.m_pointCount = 1; + manifold.m_type = b2Manifold.e_faceA; + manifold.m_localPlaneNormal.x = cLocalX - v1.x; + manifold.m_localPlaneNormal.y = cLocalY - v1.y; + manifold.m_localPlaneNormal.Normalize(); + manifold.m_localPoint.SetV(v1); + manifold.m_points[0].m_localPoint.SetV(circle.m_p); + manifold.m_points[0].m_id.key = 0; + } + else if (u2 <= 0) + { + if ((cLocalX-v2.x)*(cLocalX-v2.x)+(cLocalY-v2.y)*(cLocalY-v2.y) > radius * radius) + return; + manifold.m_pointCount = 1; + manifold.m_type = b2Manifold.e_faceA; + manifold.m_localPlaneNormal.x = cLocalX - v2.x; + manifold.m_localPlaneNormal.y = cLocalY - v2.y; + manifold.m_localPlaneNormal.Normalize(); + manifold.m_localPoint.SetV(v2); + manifold.m_points[0].m_localPoint.SetV(circle.m_p); + manifold.m_points[0].m_id.key = 0; + } + else + { + var faceCenterX:Number = 0.5 * (v1.x + v2.x); + var faceCenterY:Number = 0.5 * (v1.y + v2.y); + separation = (cLocalX - faceCenterX) * normals[vertIndex1].x + (cLocalY - faceCenterY) * normals[vertIndex1].y; + if (separation > radius) + return; + manifold.m_pointCount = 1; + manifold.m_type = b2Manifold.e_faceA; + manifold.m_localPlaneNormal.x = normals[vertIndex1].x; + manifold.m_localPlaneNormal.y = normals[vertIndex1].y; + manifold.m_localPlaneNormal.Normalize(); + manifold.m_localPoint.Set(faceCenterX,faceCenterY); + manifold.m_points[0].m_localPoint.SetV(circle.m_p); + manifold.m_points[0].m_id.key = 0; + } + } + + + + + static public function TestOverlap(a:b2AABB, b:b2AABB):Boolean + { + var t1:b2Vec2 = b.lowerBound; + var t2:b2Vec2 = a.upperBound; + //d1 = b2Math.SubtractVV(b.lowerBound, a.upperBound); + var d1X:Number = t1.x - t2.x; + var d1Y:Number = t1.y - t2.y; + //d2 = b2Math.SubtractVV(a.lowerBound, b.upperBound); + t1 = a.lowerBound; + t2 = b.upperBound; + var d2X:Number = t1.x - t2.x; + var d2Y:Number = t1.y - t2.y; + + if (d1X > 0.0 || d1Y > 0.0) + return false; + + if (d2X > 0.0 || d2Y > 0.0) + return false; + + return true; + } + + + + +} + +} diff --git a/srclib/Box2D/Collision/b2ContactID.as b/srclib/Box2D/Collision/b2ContactID.as new file mode 100644 index 00000000..bb7bc62e --- /dev/null +++ b/srclib/Box2D/Collision/b2ContactID.as @@ -0,0 +1,59 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.b2internal; + +use namespace b2internal; + +// +/** +* We use contact ids to facilitate warm starting. +*/ +public class b2ContactID +{ + public function b2ContactID(){ + features._m_id = this; + + } + public function Set(id:b2ContactID) : void{ + key = id._key; + } + public function Copy():b2ContactID{ + var id:b2ContactID = new b2ContactID(); + id.key = key; + return id; + } + public function get key():uint { + return _key; + } + public function set key(value:uint) : void { + _key = value; + features._referenceEdge = _key & 0x000000ff; + features._incidentEdge = ((_key & 0x0000ff00) >> 8) & 0x000000ff; + features._incidentVertex = ((_key & 0x00ff0000) >> 16) & 0x000000ff; + features._flip = ((_key & 0xff000000) >> 24) & 0x000000ff; + } + public var features:Features = new Features(); + /** Used to quickly compare contact ids. */ + b2internal var _key:uint; +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2ContactPoint.as b/srclib/Box2D/Collision/b2ContactPoint.as new file mode 100644 index 00000000..17d4c639 --- /dev/null +++ b/srclib/Box2D/Collision/b2ContactPoint.as @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Collision.Shapes.*; + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + +use namespace b2internal; + +/** +* This structure is used to report contact points. +*/ +public class b2ContactPoint +{ + public function b2ContactPoint() {} + + /** The first shape */ + public var shape1:b2Shape; + /** The second shape */ + public var shape2:b2Shape; + /** Position in world coordinates */ + public var position:b2Vec2 = new b2Vec2(); + /** Velocity of point on body2 relative to point on body1 (pre-solver) */ + public var velocity:b2Vec2 = new b2Vec2(); + /** Points from shape1 to shape2 */ + public var normal:b2Vec2 = new b2Vec2(); + /** The separation is negative when shapes are touching */ + public var separation:Number; + /** The combined friction coefficient */ + public var friction:Number; + /** The combined restitution coefficient */ + public var restitution:Number; + /** The contact id identifies the features in contact */ + public var id:b2ContactID = new b2ContactID(); +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2Distance.as b/srclib/Box2D/Collision/b2Distance.as new file mode 100644 index 00000000..88591451 --- /dev/null +++ b/srclib/Box2D/Collision/b2Distance.as @@ -0,0 +1,208 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.*; + import Box2D.Common.Math.*; + +use namespace b2internal; + +/** +* @private +*/ +public class b2Distance +{ + +// GJK using Voronoi regions (Christer Ericson) and Barycentric coordinates. + +private static var b2_gjkCalls:int; +private static var b2_gjkIters:int; +private static var b2_gjkMaxIters:int; + +private static var s_simplex:b2Simplex = new b2Simplex(); +private static var s_saveA:Vector. = new Vector.(3); +private static var s_saveB:Vector. = new Vector.(3); +public static function Distance(output:b2DistanceOutput, cache:b2SimplexCache, input:b2DistanceInput):void +{ + ++b2_gjkCalls; + + var proxyA:b2DistanceProxy = input.proxyA; + var proxyB:b2DistanceProxy = input.proxyB; + + var transformA:b2Transform = input.transformA; + var transformB:b2Transform = input.transformB; + + // Initialize the simplex + var simplex:b2Simplex = s_simplex; + simplex.ReadCache(cache, proxyA, transformA, proxyB, transformB); + + // Get simplex vertices as an vector. + var vertices:Vector. = simplex.m_vertices; + const k_maxIters:int = 20; + + // These store the vertices of the last simplex so that we + // can check for duplicates and preven cycling + var saveA:Vector. = s_saveA; + var saveB:Vector. = s_saveB; + var saveCount:int = 0; + + var closestPoint:b2Vec2 = simplex.GetClosestPoint(); + var distanceSqr1:Number = closestPoint.LengthSquared(); + var distanceSqr2:Number = distanceSqr1; + + var i:int; + var p:b2Vec2; + + // Main iteration loop + var iter:int = 0; + while (iter < k_maxIters) + { + // Copy the simplex so that we can identify duplicates + saveCount = simplex.m_count; + for (i = 0; i < saveCount; i++) + { + saveA[i] = vertices[i].indexA; + saveB[i] = vertices[i].indexB; + } + + switch(simplex.m_count) + { + case 1: + break; + case 2: + simplex.Solve2(); + break; + case 3: + simplex.Solve3(); + break; + default: + b2Settings.b2Assert(false); + } + + // If we have 3 points, then the origin is in the corresponding triangle. + if (simplex.m_count == 3) + { + break; + } + + // Compute the closest point. + p = simplex.GetClosestPoint(); + distanceSqr2 = p.LengthSquared(); + + // Ensure progress + if (distanceSqr2 > distanceSqr1) + { + //break; + } + distanceSqr1 = distanceSqr2; + + // Get search direction. + var d:b2Vec2 = simplex.GetSearchDirection(); + + // Ensure the search direction is numerically fit. + if (d.LengthSquared() < Number.MIN_VALUE * Number.MIN_VALUE) + { + // THe origin is probably contained by a line segment or triangle. + // Thus the shapes are overlapped. + + // We can't return zero here even though there may be overlap. + // In case the simplex is a point, segment or triangle it is very difficult + // to determine if the origin is contained in the CSO or very close to it + break; + } + + // Compute a tentative new simplex vertex using support points + var vertex:b2SimplexVertex = vertices[simplex.m_count]; + vertex.indexA = proxyA.GetSupport(b2Math.MulTMV(transformA.R, d.GetNegative())); + vertex.wA = b2Math.MulX(transformA, proxyA.GetVertex(vertex.indexA)); + vertex.indexB = proxyB.GetSupport(b2Math.MulTMV(transformB.R, d)); + vertex.wB = b2Math.MulX(transformB, proxyB.GetVertex(vertex.indexB)); + vertex.w = b2Math.SubtractVV(vertex.wB, vertex.wA); + + // Iteration count is equated to the number of support point calls. + ++iter; + ++b2_gjkIters; + + // Check for duplicate support points. This is the main termination criteria. + var duplicate:Boolean = false; + for (i = 0; i < saveCount; i++) + { + if (vertex.indexA == saveA[i] && vertex.indexB == saveB[i]) + { + duplicate = true; + break; + } + } + + // If we found a duplicate support point we must exist to avoid cycling + if (duplicate) + { + break; + } + + // New vertex is ok and needed. + ++simplex.m_count; + } + + b2_gjkMaxIters = b2Math.Max(b2_gjkMaxIters, iter); + + // Prepare output + simplex.GetWitnessPoints(output.pointA, output.pointB); + output.distance = b2Math.SubtractVV(output.pointA, output.pointB).Length(); + output.iterations = iter; + + // Cache the simplex + simplex.WriteCache(cache); + + // Apply radii if requested. + if (input.useRadii) + { + var rA:Number = proxyA.m_radius; + var rB:Number = proxyB.m_radius; + + if (output.distance > rA + rB && output.distance > Number.MIN_VALUE) + { + // Shapes are still not overlapped. + // Move the witness points to the outer surface. + output.distance -= rA + rB; + var normal:b2Vec2 = b2Math.SubtractVV(output.pointB, output.pointA); + normal.Normalize(); + output.pointA.x += rA * normal.x; + output.pointA.y += rA * normal.y; + output.pointB.x -= rB * normal.x; + output.pointB.y -= rB * normal.y; + } + else + { + // Shapes are overlapped when radii are considered. + // Move the witness points to the middle. + p = new b2Vec2(); + p.x = .5 * (output.pointA.x + output.pointB.x); + p.y = .5 * (output.pointA.y + output.pointB.y); + output.pointA.x = output.pointB.x = p.x; + output.pointA.y = output.pointB.y = p.y; + output.distance = 0.0; + } + } +} + +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2DistanceInput.as b/srclib/Box2D/Collision/b2DistanceInput.as new file mode 100644 index 00000000..8be94f65 --- /dev/null +++ b/srclib/Box2D/Collision/b2DistanceInput.as @@ -0,0 +1,39 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ +package Box2D.Collision +{ + + import Box2D.Common.Math.b2Transform; + + /** + * Input for b2Distance. + * You have to option to use the shape radii + * in the computation. Even + */ + public class b2DistanceInput + { + public function b2DistanceInput() {} + + public var proxyA:b2DistanceProxy; + public var proxyB:b2DistanceProxy; + public var transformA:b2Transform; + public var transformB:b2Transform; + public var useRadii:Boolean; + } + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2DistanceOutput.as b/srclib/Box2D/Collision/b2DistanceOutput.as new file mode 100644 index 00000000..4bffdbaa --- /dev/null +++ b/srclib/Box2D/Collision/b2DistanceOutput.as @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ +package Box2D.Collision +{ + + import Box2D.Common.Math.b2Vec2; + + /** + * Output for b2Distance. + */ + public class b2DistanceOutput + { + public function b2DistanceOutput() {} + + /** Closest point on shapea */ public var pointA:b2Vec2 = new b2Vec2(); /** Closest point on shapeb */ public var pointB:b2Vec2 = new b2Vec2(); public var distance:Number; + /** Number of gjk iterations used */ public var iterations:int; } + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2DistanceProxy.as b/srclib/Box2D/Collision/b2DistanceProxy.as new file mode 100644 index 00000000..9d6fe4df --- /dev/null +++ b/srclib/Box2D/Collision/b2DistanceProxy.as @@ -0,0 +1,125 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision +{ + + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; + +use namespace b2internal; + + /** + * A distance proxy is used by the GJK algorithm. + * It encapsulates any shape. + */ + public class b2DistanceProxy + { + public function b2DistanceProxy() {} + + /** + * Initialize the proxy using the given shape. The shape + * must remain in scope while the proxy is in use. + */ + public function Set(shape:b2Shape):void + { + switch(shape.GetType()) + { + case b2Shape.e_circleShape: + { + var circle:b2CircleShape = shape as b2CircleShape; + m_vertices = new Vector.(1, true); + m_vertices[0] = circle.m_p; + m_count = 1; + m_radius = circle.m_radius; + } + break; + case b2Shape.e_polygonShape: + { + var polygon:b2PolygonShape = shape as b2PolygonShape; + m_vertices = polygon.m_vertices; + m_count = polygon.m_vertexCount; + m_radius = polygon.m_radius; + } + break; + default: + b2Settings.b2Assert(false); + } + } + + /** + * Get the supporting vertex index in the given direction. + */ + public function GetSupport(d:b2Vec2):Number + { + var bestIndex:int = 0; + var bestValue:Number = m_vertices[0].x * d.x + m_vertices[0].y * d.y; + for (var i:int= 1; i < m_count; ++i) + { + var value:Number = m_vertices[i].x * d.x + m_vertices[i].y * d.y; + if (value > bestValue) + { + bestIndex = i; + bestValue = value; + } + } + return bestIndex; + } + + /** + * Get the supporting vertex in the given direction. + */ + public function GetSupportVertex(d:b2Vec2):b2Vec2 + { + var bestIndex:int = 0; + var bestValue:Number = m_vertices[0].x * d.x + m_vertices[0].y * d.y; + for (var i:int= 1; i < m_count; ++i) + { + var value:Number = m_vertices[i].x * d.x + m_vertices[i].y * d.y; + if (value > bestValue) + { + bestIndex = i; + bestValue = value; + } + } + return m_vertices[bestIndex]; + } + /** + * Get the vertex count. + */ + public function GetVertexCount():int + { + return m_count; + } + + /** + * Get a vertex by index. Used by b2Distance. + */ + public function GetVertex(index:int):b2Vec2 + { + b2Settings.b2Assert(0 <= index && index < m_count); + return m_vertices[index]; + } + + public var m_vertices:Vector.; + public var m_count:int; + public var m_radius:Number; + } + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2DynamicTree.as b/srclib/Box2D/Collision/b2DynamicTree.as new file mode 100644 index 00000000..97000938 --- /dev/null +++ b/srclib/Box2D/Collision/b2DynamicTree.as @@ -0,0 +1,476 @@ +/* +* Copyright (c) 2009 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision +{ + + import Box2D.Common.*; + import Box2D.Common.Math.*; + + // A dynamic AABB tree broad-phase, inspired by Nathanael Presson's btDbvt. + + /** + * A dynamic tree arranges data in a binary tree to accelerate + * queries such as volume queries and ray casts. Leafs are proxies + * with an AABB. In the tree we expand the proxy AABB by b2_fatAABBFactor + * so that the proxy AABB is bigger than the client object. This allows the client + * object to move by small amounts without triggering a tree update. + * + * Nodes are pooled. + */ + public class b2DynamicTree + { + /** + * Constructing the tree initializes the node pool. + */ + public function b2DynamicTree() + { + m_root = null; + + // TODO: Maybe allocate some free nodes? + m_freeList = null; + m_path = 0; + + m_insertionCount = 0; + } + /* + public function Dump(node:b2DynamicTreeNode=null, depth:int=0):void + { + if (!node) + { + node = m_root; + } + if (!node) return; + for (var i:int = 0; i < depth; i++) s += " "; + if (node.userData) + { + var ud:* = (node.userData as b2Fixture).GetBody().GetUserData(); + trace(s + ud); + }else { + trace(s + "-"); + } + if (node.child1) + Dump(node.child1, depth + 1); + if (node.child2) + Dump(node.child2, depth + 1); + } + */ + + /** + * Create a proxy. Provide a tight fitting AABB and a userData. + */ + public function CreateProxy(aabb:b2AABB, userData:*):b2DynamicTreeNode + { + var node:b2DynamicTreeNode = AllocateNode(); + + // Fatten the aabb. + var extendX:Number = b2Settings.b2_aabbExtension; + var extendY:Number = b2Settings.b2_aabbExtension; + node.aabb.lowerBound.x = aabb.lowerBound.x - extendX; + node.aabb.lowerBound.y = aabb.lowerBound.y - extendY; + node.aabb.upperBound.x = aabb.upperBound.x + extendX; + node.aabb.upperBound.y = aabb.upperBound.y + extendY; + + node.userData = userData; + + InsertLeaf(node); + return node; + } + + /** + * Destroy a proxy. This asserts if the id is invalid. + */ + public function DestroyProxy(proxy:b2DynamicTreeNode):void + { + //b2Settings.b2Assert(proxy.IsLeaf()); + RemoveLeaf(proxy); + FreeNode(proxy); + } + + /** + * Move a proxy with a swept AABB. If the proxy has moved outside of its fattened AABB, + * then the proxy is removed from the tree and re-inserted. Otherwise + * the function returns immediately. + */ + public function MoveProxy(proxy:b2DynamicTreeNode, aabb:b2AABB, displacement:b2Vec2):Boolean + { + b2Settings.b2Assert(proxy.IsLeaf()); + + if (proxy.aabb.Contains(aabb)) + { + return false; + } + + RemoveLeaf(proxy); + + // Extend AABB + var extendX:Number = b2Settings.b2_aabbExtension + b2Settings.b2_aabbMultiplier * (displacement.x > 0?displacement.x: -displacement.x); + var extendY:Number = b2Settings.b2_aabbExtension + b2Settings.b2_aabbMultiplier * (displacement.y > 0?displacement.y: -displacement.y); + proxy.aabb.lowerBound.x = aabb.lowerBound.x - extendX; + proxy.aabb.lowerBound.y = aabb.lowerBound.y - extendY; + proxy.aabb.upperBound.x = aabb.upperBound.x + extendX; + proxy.aabb.upperBound.y = aabb.upperBound.y + extendY; + + InsertLeaf(proxy); + return true; + } + + /** + * Perform some iterations to re-balance the tree. + */ + public function Rebalance(iterations:int):void + { + if (m_root == null) + return; + + for (var i:int = 0; i < iterations; i++) + { + var node:b2DynamicTreeNode = m_root; + var bit:uint = 0; + while (node.IsLeaf() == false) + { + node = (m_path >> bit) & 1 ? node.child2 : node.child1; + bit = (bit + 1) & 31; // 0-31 bits in a uint + } + ++m_path; + + RemoveLeaf(node); + InsertLeaf(node); + } + } + + public function GetFatAABB(proxy:b2DynamicTreeNode):b2AABB + { + return proxy.aabb; + } + + /** + * Get user data from a proxy. Returns null if the proxy is invalid. + */ + public function GetUserData(proxy:b2DynamicTreeNode):* + { + return proxy.userData; + } + + /** + * Query an AABB for overlapping proxies. The callback + * is called for each proxy that overlaps the supplied AABB. + * The callback should match function signature + * fuction callback(proxy:b2DynamicTreeNode):Boolean + * and should return false to trigger premature termination. + */ + public function Query(callback:Function, aabb:b2AABB):void + { + if (m_root == null) + return; + + var stack:Vector. = new Vector.(); + + var count:int = 0; + stack[count++] = m_root; + + while (count > 0) + { + var node:b2DynamicTreeNode = stack[--count]; + + if (node.aabb.TestOverlap(aabb)) + { + if (node.IsLeaf()) + { + var proceed:Boolean = callback(node); + if (!proceed) + return; + } + else + { + // No stack limit, so no assert + stack[count++] = node.child1; + stack[count++] = node.child2; + } + } + } + } + + /** + * Ray-cast against the proxies in the tree. This relies on the callback + * to perform a exact ray-cast in the case were the proxy contains a shape. + * The callback also performs the any collision filtering. This has performance + * roughly equal to k * log(n), where k is the number of collisions and n is the + * number of proxies in the tree. + * @param input the ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). + * @param callback a callback class that is called for each proxy that is hit by the ray. + * It should be of signature: + * function callback(input:b2RayCastInput, proxy:*):void + */ + public function RayCast(callback:Function, input:b2RayCastInput):void + { + if (m_root == null) + return; + + var p1:b2Vec2 = input.p1; + var p2:b2Vec2 = input.p2; + var r:b2Vec2 = b2Math.SubtractVV(p1, p2); + //b2Settings.b2Assert(r.LengthSquared() > 0.0); + r.Normalize(); + + // v is perpendicular to the segment + var v:b2Vec2 = b2Math.CrossFV(1.0, r); + var abs_v:b2Vec2 = b2Math.AbsV(v); + + var maxFraction:Number = input.maxFraction; + + // Build a bounding box for the segment + var segmentAABB:b2AABB = new b2AABB(); + var tX:Number; + var tY:Number; + { + tX = p1.x + maxFraction * (p2.x - p1.x); + tY = p1.y + maxFraction * (p2.y - p1.y); + segmentAABB.lowerBound.x = Math.min(p1.x, tX); + segmentAABB.lowerBound.y = Math.min(p1.y, tY); + segmentAABB.upperBound.x = Math.max(p1.x, tX); + segmentAABB.upperBound.y = Math.max(p1.y, tY); + } + + var stack:Vector. = new Vector.(); + + var count:int = 0; + stack[count++] = m_root; + + while (count > 0) + { + var node:b2DynamicTreeNode = stack[--count]; + + if (node.aabb.TestOverlap(segmentAABB) == false) + { + continue; + } + + // Separating axis for segment (Gino, p80) + // |dot(v, p1 - c)| > dot(|v|,h) + + var c:b2Vec2 = node.aabb.GetCenter(); + var h:b2Vec2 = node.aabb.GetExtents(); + var separation:Number = Math.abs(v.x * (p1.x - c.x) + v.y * (p1.y - c.y)) + - abs_v.x * h.x - abs_v.y * h.y; + if (separation > 0.0) + continue; + + if (node.IsLeaf()) + { + var subInput:b2RayCastInput = new b2RayCastInput(); + subInput.p1 = input.p1; + subInput.p2 = input.p2; + subInput.maxFraction = input.maxFraction; + + maxFraction = callback(subInput, node); + + if (maxFraction == 0.0) + return; + + //Update the segment bounding box + { + tX = p1.x + maxFraction * (p2.x - p1.x); + tY = p1.y + maxFraction * (p2.y - p1.y); + segmentAABB.lowerBound.x = Math.min(p1.x, tX); + segmentAABB.lowerBound.y = Math.min(p1.y, tY); + segmentAABB.upperBound.x = Math.max(p1.x, tX); + segmentAABB.upperBound.y = Math.max(p1.y, tY); + } + } + else + { + // No stack limit, so no assert + stack[count++] = node.child1; + stack[count++] = node.child2; + } + } + } + + + private function AllocateNode():b2DynamicTreeNode + { + // Peel a node off the free list + if (m_freeList) + { + var node:b2DynamicTreeNode = m_freeList; + m_freeList = node.parent; + node.parent = null; + node.child1 = null; + node.child2 = null; + return node; + } + + // Ignore length pool expansion and relocation found in the C++ + // As we are using heap allocation + return new b2DynamicTreeNode(); + } + + private function FreeNode(node:b2DynamicTreeNode):void + { + node.parent = m_freeList; + m_freeList = node; + } + + private function InsertLeaf(leaf:b2DynamicTreeNode):void + { + ++m_insertionCount; + + if (m_root == null) + { + m_root = leaf; + m_root.parent = null; + return; + } + + var center:b2Vec2 = leaf.aabb.GetCenter(); + var sibling:b2DynamicTreeNode = m_root; + if (sibling.IsLeaf() == false) + { + do + { + var child1:b2DynamicTreeNode = sibling.child1; + var child2:b2DynamicTreeNode = sibling.child2; + + //b2Vec2 delta1 = b2Abs(m_nodes[child1].aabb.GetCenter() - center); + //b2Vec2 delta2 = b2Abs(m_nodes[child2].aabb.GetCenter() - center); + //float32 norm1 = delta1.x + delta1.y; + //float32 norm2 = delta2.x + delta2.y; + + var norm1:Number = Math.abs((child1.aabb.lowerBound.x + child1.aabb.upperBound.x) / 2 - center.x) + + Math.abs((child1.aabb.lowerBound.y + child1.aabb.upperBound.y) / 2 - center.y); + var norm2:Number = Math.abs((child2.aabb.lowerBound.x + child2.aabb.upperBound.x) / 2 - center.x) + + Math.abs((child2.aabb.lowerBound.y + child2.aabb.upperBound.y) / 2 - center.y); + + if (norm1 < norm2) + { + sibling = child1; + }else { + sibling = child2; + } + } + while (sibling.IsLeaf() == false); + } + + // Create a parent for the siblings + var node1:b2DynamicTreeNode = sibling.parent; + var node2:b2DynamicTreeNode = AllocateNode(); + node2.parent = node1; + node2.userData = null; + node2.aabb.Combine(leaf.aabb, sibling.aabb); + if (node1) + { + if (sibling.parent.child1 == sibling) + { + node1.child1 = node2; + } + else + { + node1.child2 = node2; + } + + node2.child1 = sibling; + node2.child2 = leaf; + sibling.parent = node2; + leaf.parent = node2; + do + { + if (node1.aabb.Contains(node2.aabb)) + break; + + node1.aabb.Combine(node1.child1.aabb, node1.child2.aabb); + node2 = node1; + node1 = node1.parent; + } + while (node1); + } + else + { + node2.child1 = sibling; + node2.child2 = leaf; + sibling.parent = node2; + leaf.parent = node2; + m_root = node2; + } + + } + + private function RemoveLeaf(leaf:b2DynamicTreeNode):void + { + if ( leaf == m_root) + { + m_root = null; + return; + } + + var node2:b2DynamicTreeNode = leaf.parent; + var node1:b2DynamicTreeNode = node2.parent; + var sibling:b2DynamicTreeNode; + if (node2.child1 == leaf) + { + sibling = node2.child2; + } + else + { + sibling = node2.child1; + } + + if (node1) + { + // Destroy node2 and connect node1 to sibling + if (node1.child1 == node2) + { + node1.child1 = sibling; + } + else + { + node1.child2 = sibling; + } + sibling.parent = node1; + FreeNode(node2); + + // Adjust the ancestor bounds + while (node1) + { + var oldAABB:b2AABB = node1.aabb; + node1.aabb = b2AABB.Combine(node1.child1.aabb, node1.child2.aabb); + + if (oldAABB.Contains(node1.aabb)) + break; + + node1 = node1.parent; + } + } + else + { + m_root = sibling; + sibling.parent = null; + FreeNode(node2); + } + } + + private var m_root:b2DynamicTreeNode; + private var m_freeList:b2DynamicTreeNode; + + /** This is used for incrementally traverse the tree for rebalancing */ + private var m_path:uint; + + private var m_insertionCount:int; + } + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2DynamicTreeBroadPhase.as b/srclib/Box2D/Collision/b2DynamicTreeBroadPhase.as new file mode 100644 index 00000000..4659bee2 --- /dev/null +++ b/srclib/Box2D/Collision/b2DynamicTreeBroadPhase.as @@ -0,0 +1,200 @@ +package Box2D.Collision +{ + + import Box2D.Common.Math.*; + +/** + * The broad-phase is used for computing pairs and performing volume queries and ray casts. + * This broad-phase does not persist pairs. Instead, this reports potentially new pairs. + * It is up to the client to consume the new pairs and to track subsequent overlap. + */ +public class b2DynamicTreeBroadPhase implements IBroadPhase +{ + public function b2DynamicTreeBroadPhase() {} + + /** + * Create a proxy with an initial AABB. Pairs are not reported until + * UpdatePairs is called. + */ + public function CreateProxy(aabb:b2AABB, userData:*):* + { + var proxy:b2DynamicTreeNode = m_tree.CreateProxy(aabb, userData); + ++m_proxyCount; + BufferMove(proxy); + return proxy; + } + + /** + * Destroy a proxy. It is up to the client to remove any pairs. + */ + public function DestroyProxy(proxy:*):void + { + UnBufferMove(proxy); + --m_proxyCount; + m_tree.DestroyProxy(proxy); + } + + /** + * Call MoveProxy as many times as you like, then when you are done + * call UpdatePairs to finalized the proxy pairs (for your time step). + */ + public function MoveProxy(proxy:*, aabb:b2AABB, displacement:b2Vec2):void + { + var buffer:Boolean = m_tree.MoveProxy(proxy, aabb, displacement); + if (buffer) + { + BufferMove(proxy); + } + } + + public function TestOverlap(proxyA:*, proxyB:*):Boolean + { + var aabbA:b2AABB = m_tree.GetFatAABB(proxyA); + var aabbB:b2AABB = m_tree.GetFatAABB(proxyB); + return aabbA.TestOverlap(aabbB); + } + + /** + * Get user data from a proxy. Returns null if the proxy is invalid. + */ + public function GetUserData(proxy:*):* + { + return m_tree.GetUserData(proxy); + } + + /** + * Get the AABB for a proxy. + */ + public function GetFatAABB(proxy:*):b2AABB + { + return m_tree.GetFatAABB(proxy); + } + + /** + * Get the number of proxies. + */ + public function GetProxyCount():int + { + return m_proxyCount; + } + + /** + * Update the pairs. This results in pair callbacks. This can only add pairs. + */ + public function UpdatePairs(callback:Function):void + { + m_pairCount = 0; + // Perform tree queries for all moving queries + for each(var queryProxy:b2DynamicTreeNode in m_moveBuffer) + { + function QueryCallback(proxy:b2DynamicTreeNode):Boolean + { + // A proxy cannot form a pair with itself. + if (proxy == queryProxy) + return true; + + // Grow the pair buffer as needed + if (m_pairCount == m_pairBuffer.length) + { + m_pairBuffer[m_pairCount] = new b2DynamicTreePair(); + } + + var pair:b2DynamicTreePair = m_pairBuffer[m_pairCount]; + pair.proxyA = proxy < queryProxy?proxy:queryProxy; + pair.proxyB = proxy >= queryProxy?proxy:queryProxy; + ++m_pairCount; + + return true; + } + // We have to query the tree with the fat AABB so that + // we don't fail to create a pair that may touch later. + var fatAABB:b2AABB = m_tree.GetFatAABB(queryProxy); + m_tree.Query(QueryCallback, fatAABB); + } + + // Reset move buffer + m_moveBuffer.length = 0; + + // Sort the pair buffer to expose duplicates. + // TODO: Something more sensible + //m_pairBuffer.sort(ComparePairs); + + // Send the pair buffer + for (var i:int = 0; i < m_pairCount; ) + { + var primaryPair:b2DynamicTreePair = m_pairBuffer[i]; + var userDataA:* = m_tree.GetUserData(primaryPair.proxyA); + var userDataB:* = m_tree.GetUserData(primaryPair.proxyB); + callback(userDataA, userDataB); + ++i; + + // Skip any duplicate pairs + while (i < m_pairCount) + { + var pair:b2DynamicTreePair = m_pairBuffer[i]; + if (pair.proxyA != primaryPair.proxyA || pair.proxyB != primaryPair.proxyB) + { + break; + } + ++i; + } + } + } + + /** + * @inheritDoc + */ + public function Query(callback:Function, aabb:b2AABB):void + { + m_tree.Query(callback, aabb); + } + + /** + * @inheritDoc + */ + public function RayCast(callback:Function, input:b2RayCastInput):void + { + m_tree.RayCast(callback, input); + } + + + public function Validate():void + { + //TODO_BORIS + } + + public function Rebalance(iterations:int):void + { + m_tree.Rebalance(iterations); + } + + + // Private /////////////// + + private function BufferMove(proxy:b2DynamicTreeNode):void + { + m_moveBuffer[m_moveBuffer.length] = proxy; + } + + private function UnBufferMove(proxy:b2DynamicTreeNode):void + { + var i:int = m_moveBuffer.indexOf(proxy); + m_moveBuffer.splice(i, 1); + } + + private function ComparePairs(pair1:b2DynamicTreePair, pair2:b2DynamicTreePair):int + { + //TODO_BORIS: + // We cannot consistently sort objects easily in AS3 + // The caller of this needs replacing with a different method. + return 0; + } + private var m_tree:b2DynamicTree = new b2DynamicTree(); + private var m_proxyCount:int; + private var m_moveBuffer:Vector. = new Vector.(); + + private var m_pairBuffer:Vector. = new Vector.(); + private var m_pairCount:int = 0; +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2DynamicTreeNode.as b/srclib/Box2D/Collision/b2DynamicTreeNode.as new file mode 100644 index 00000000..0d77b22a --- /dev/null +++ b/srclib/Box2D/Collision/b2DynamicTreeNode.as @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2009 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision +{ + + /** + * A node in the dynamic tree. The client does not interact with this directly. + * @private + */ + public class b2DynamicTreeNode + { + public function b2DynamicTreeNode() {} + + public function IsLeaf():Boolean + { + return child1 == null; + } + + public var userData:*; + public var aabb:b2AABB = new b2AABB(); + public var parent:b2DynamicTreeNode; + public var child1:b2DynamicTreeNode; + public var child2:b2DynamicTreeNode; + } + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2DynamicTreePair.as b/srclib/Box2D/Collision/b2DynamicTreePair.as new file mode 100644 index 00000000..45c1b08b --- /dev/null +++ b/srclib/Box2D/Collision/b2DynamicTreePair.as @@ -0,0 +1,14 @@ +package Box2D.Collision +{ + /** + * @private + */ + public class b2DynamicTreePair + { + public function b2DynamicTreePair() {} + + public var proxyA:b2DynamicTreeNode; + public var proxyB:b2DynamicTreeNode; + } + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2Manifold.as b/srclib/Box2D/Collision/b2Manifold.as new file mode 100644 index 00000000..9ccbf4f2 --- /dev/null +++ b/srclib/Box2D/Collision/b2Manifold.as @@ -0,0 +1,95 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.*; + import Box2D.Common.Math.*; + +use namespace b2internal; + +/** + * A manifold for two touching convex shapes. + * Box2D supports multiple types of contact: + * - clip point versus plane with radius + * - point versus point with radius (circles) + * The local point usage depends on the manifold type: + * -e_circles: the local center of circleA + * -e_faceA: the center of faceA + * -e_faceB: the center of faceB + * Similarly the local normal usage: + * -e_circles: not used + * -e_faceA: the normal on polygonA + * -e_faceB: the normal on polygonB + * We store contacts in this way so that position correction can + * account for movement, which is critical for continuous physics. + * All contact scenarios must be expressed in one of these types. + * This structure is stored across time steps, so we keep it small. + */ +public class b2Manifold +{ + public function b2Manifold(){ + m_points = new Vector.(b2Settings.b2_maxManifoldPoints); + for (var i:int = 0; i < b2Settings.b2_maxManifoldPoints; i++){ + m_points[i] = new b2ManifoldPoint(); + } + m_localPlaneNormal = new b2Vec2(); + m_localPoint = new b2Vec2(); + } + public function Reset() : void{ + for (var i:int = 0; i < b2Settings.b2_maxManifoldPoints; i++){ + (m_points[i] as b2ManifoldPoint).Reset(); + } + m_localPlaneNormal.SetZero(); + m_localPoint.SetZero(); + m_type = 0; + m_pointCount = 0; + } + public function Set(m:b2Manifold) : void{ + m_pointCount = m.m_pointCount; + for (var i:int = 0; i < b2Settings.b2_maxManifoldPoints; i++){ + (m_points[i] as b2ManifoldPoint).Set(m.m_points[i]); + } + m_localPlaneNormal.SetV(m.m_localPlaneNormal); + m_localPoint.SetV(m.m_localPoint); + m_type = m.m_type; + } + public function Copy():b2Manifold + { + var copy:b2Manifold = new b2Manifold(); + copy.Set(this); + return copy; + } + /** The points of contact */ + public var m_points:Vector.; + /** Not used for Type e_points*/ + public var m_localPlaneNormal:b2Vec2; + /** Usage depends on manifold type */ + public var m_localPoint:b2Vec2; + public var m_type:int; + /** The number of manifold points */ + public var m_pointCount:int = 0; + + //enum Type + public static const e_circles:int = 0x0001; + public static const e_faceA:int = 0x0002; + public static const e_faceB:int = 0x0004; +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2ManifoldPoint.as b/srclib/Box2D/Collision/b2ManifoldPoint.as new file mode 100644 index 00000000..6b21a3db --- /dev/null +++ b/srclib/Box2D/Collision/b2ManifoldPoint.as @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + +use namespace b2internal; + +/** + * A manifold point is a contact point belonging to a contact + * manifold. It holds details related to the geometry and dynamics + * of the contact points. + * The local point usage depends on the manifold type: + * -e_circles: the local center of circleB + * -e_faceA: the local center of cirlceB or the clip point of polygonB + * -e_faceB: the clip point of polygonA + * This structure is stored across time steps, so we keep it small. + * Note: the impulses are used for internal caching and may not + * provide reliable contact forces, especially for high speed collisions. + */ +public class b2ManifoldPoint +{ + public function b2ManifoldPoint() + { + Reset(); + } + public function Reset() : void{ + m_localPoint.SetZero(); + m_normalImpulse = 0.0; + m_tangentImpulse = 0.0; + m_id.key = 0; + } + public function Set(m:b2ManifoldPoint) : void{ + m_localPoint.SetV(m.m_localPoint); + m_normalImpulse = m.m_normalImpulse; + m_tangentImpulse = m.m_tangentImpulse; + m_id.Set(m.m_id); + } + public var m_localPoint:b2Vec2 = new b2Vec2(); + public var m_normalImpulse:Number; + public var m_tangentImpulse:Number; + public var m_id:b2ContactID = new b2ContactID(); +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2OBB.as b/srclib/Box2D/Collision/b2OBB.as new file mode 100644 index 00000000..9b06292a --- /dev/null +++ b/srclib/Box2D/Collision/b2OBB.as @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + +use namespace b2internal; + +/** +* An oriented bounding box. +*/ +public class b2OBB +{ + public function b2OBB() {} + + /** The rotation matrix */ + public var R:b2Mat22 = new b2Mat22(); + /** The local centroid */ + public var center:b2Vec2 = new b2Vec2(); + /** The half-widths */ + public var extents:b2Vec2 = new b2Vec2(); +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2Pair.as b/srclib/Box2D/Collision/b2Pair.as new file mode 100644 index 00000000..6bcec04c --- /dev/null +++ b/srclib/Box2D/Collision/b2Pair.as @@ -0,0 +1,66 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +// The pair manager is used by the broad-phase to quickly add/remove/find pairs +// of overlapping proxies. It is based closely on code provided by Pierre Terdiman. +// http://www.codercorner.com/IncrementalSAP.txt + +package Box2D.Collision{ + + + import Box2D.Common.*; +use namespace b2internal; + + +/** + * A Pair represents a pair of overlapping b2Proxy in the broadphse. + * @private + */ +public class b2Pair +{ + public function b2Pair() {} + + public function SetBuffered() : void { status |= e_pairBuffered; } + public function ClearBuffered() : void { status &= ~e_pairBuffered; } + public function IsBuffered():Boolean { return (status & e_pairBuffered) == e_pairBuffered; } + + public function SetRemoved() : void { status |= e_pairRemoved; } + public function ClearRemoved() : void { status &= ~e_pairRemoved; } + public function IsRemoved():Boolean { return (status & e_pairRemoved) == e_pairRemoved; } + + public function SetFinal() : void { status |= e_pairFinal; } + public function IsFinal():Boolean { return (status & e_pairFinal) == e_pairFinal; } + + public var userData:* = null; + public var proxy1:b2Proxy; + public var proxy2:b2Proxy; + public var next:b2Pair; + public var status:uint; + + // STATIC + static public var b2_nullProxy:uint = b2Settings.USHRT_MAX; + + // enum + static public var e_pairBuffered:uint = 0x0001; + static public var e_pairRemoved:uint = 0x0002; + static public var e_pairFinal:uint = 0x0004; + +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2PairManager.as b/srclib/Box2D/Collision/b2PairManager.as new file mode 100644 index 00000000..c4020d46 --- /dev/null +++ b/srclib/Box2D/Collision/b2PairManager.as @@ -0,0 +1,272 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +// The pair manager is used by the broad-phase to quickly add/remove/find pairs +// of overlapping proxies. It is based closely on code provided by Pierre Terdiman. +// http://www.codercorner.com/IncrementalSAP.txt + +package Box2D.Collision{ + + + import Box2D.Common.*; +use namespace b2internal; + + +/** +* @private +*/ +public class b2PairManager +{ +//public: + public function b2PairManager(){ + m_pairs = new Array(); + m_pairBuffer = new Array(); + m_pairCount = 0; + m_pairBufferCount = 0; + m_freePair = null; + } + //~b2PairManager(); + + public function Initialize(broadPhase:b2BroadPhase) : void{ + m_broadPhase = broadPhase; + } + + /* + As proxies are created and moved, many pairs are created and destroyed. Even worse, the same + pair may be added and removed multiple times in a single time step of the physics engine. To reduce + traffic in the pair manager, we try to avoid destroying pairs in the pair manager until the + end of the physics step. This is done by buffering all the RemovePair requests. AddPair + requests are processed immediately because we need the hash table entry for quick lookup. + + All user user callbacks are delayed until the buffered pairs are confirmed in Commit. + This is very important because the user callbacks may be very expensive and client logic + may be harmed if pairs are added and removed within the same time step. + + Buffer a pair for addition. + We may add a pair that is not in the pair manager or pair buffer. + We may add a pair that is already in the pair manager and pair buffer. + If the added pair is not a new pair, then it must be in the pair buffer (because RemovePair was called). + */ + public function AddBufferedPair(proxy1:b2Proxy, proxy2:b2Proxy) : void{ + //b2Settings.b2Assert(proxy1 && proxy2); + + var pair:b2Pair = AddPair(proxy1, proxy2); + + // If this pair is not in the pair buffer ... + if (pair.IsBuffered() == false) + { + // This must be a newly added pair. + //b2Settings.b2Assert(pair.IsFinal() == false); + + // Add it to the pair buffer. + pair.SetBuffered(); + m_pairBuffer[m_pairBufferCount] = pair; + ++m_pairBufferCount; + //b2Settings.b2Assert(m_pairBufferCount <= m_pairCount); + } + + // Confirm this pair for the subsequent call to Commit. + pair.ClearRemoved(); + + if (b2BroadPhase.s_validate) + { + ValidateBuffer(); + } + } + + // Buffer a pair for removal. + public function RemoveBufferedPair(proxy1:b2Proxy, proxy2:b2Proxy) : void{ + //b2Settings.b2Assert(proxy1 && proxy2); + + var pair:b2Pair = Find(proxy1, proxy2); + + if (pair == null) + { + // The pair never existed. This is legal (due to collision filtering). + return; + } + + // If this pair is not in the pair buffer ... + if (pair.IsBuffered() == false) + { + // This must be an old pair. + //b2Settings.b2Assert(pair.IsFinal() == true); + + pair.SetBuffered(); + m_pairBuffer[m_pairBufferCount] = pair; + ++m_pairBufferCount; + + //b2Settings.b2Assert(m_pairBufferCount <= m_pairCount); + } + + pair.SetRemoved(); + + if (b2BroadPhase.s_validate) + { + ValidateBuffer(); + } + } + + public function Commit(callback:Function) : void{ + var i:int; + + var removeCount:int = 0; + + for (i = 0; i < m_pairBufferCount; ++i) + { + var pair:b2Pair = m_pairBuffer[i]; + //b2Settings.b2Assert(pair.IsBuffered()); + pair.ClearBuffered(); + + //b2Settings.b2Assert(pair.proxy1 && pair.proxy2); + + var proxy1:b2Proxy = pair.proxy1; + var proxy2:b2Proxy = pair.proxy2; + + //b2Settings.b2Assert(proxy1.IsValid()); + //b2Settings.b2Assert(proxy2.IsValid()); + + if (pair.IsRemoved()) + { + // It is possible a pair was added then removed before a commit. Therefore, + // we should be careful not to tell the user the pair was removed when the + // the user didn't receive a matching add. + //if (pair.IsFinal() == true) + //{ + // m_callback.PairRemoved(proxy1.userData, proxy2.userData, pair.userData); + //} + + // Store the ids so we can actually remove the pair below. + //m_pairBuffer[removeCount] = pair; + //++removeCount; + } + else + { + //b2Settings.b2Assert(m_broadPhase.TestOverlap(proxy1, proxy2) == true); + + if (pair.IsFinal() == false) + { + //pair.userData = m_callback.PairAdded(proxy1.userData, proxy2.userData); + //pair.SetFinal(); + callback(proxy1.userData, proxy2.userData); + } + } + } + + //for (i = 0; i < removeCount; ++i) + //{ + // pair = m_pairBuffer[i] + // RemovePair(pair.proxy1, pair.proxy2); + //} + + m_pairBufferCount = 0; + + if (b2BroadPhase.s_validate) + { + ValidateTable(); + } + } + +//private: + + // Add a pair and return the new pair. If the pair already exists, + // no new pair is created and the old one is returned. + private function AddPair(proxy1:b2Proxy, proxy2:b2Proxy):b2Pair + { + var pair:b2Pair = proxy1.pairs[proxy2]; + + if (pair != null) + return pair; + + if (m_freePair == null) + { + m_freePair = new b2Pair(); + m_pairs.push(m_freePair); + } + pair = m_freePair; + m_freePair = pair.next; + + pair.proxy1 = proxy1; + pair.proxy2 = proxy2; + pair.status = 0; + pair.userData = null; + pair.next = null; + + proxy1.pairs[proxy2] = pair; + proxy2.pairs[proxy1] = pair; + + ++m_pairCount; + + return pair; + } + + // Remove a pair, return the pair's userData. + private function RemovePair(proxy1:b2Proxy, proxy2:b2Proxy):* + { + //b2Settings.b2Assert(m_pairCount > 0); + + var pair:b2Pair = proxy1.pairs[proxy2]; + + if (pair == null) + { + //b2Settings.b2Assert(false); + return null; + } + + var userData:* = pair.userData; + + delete proxy1.pairs[proxy2]; + delete proxy2.pairs[proxy1]; + + // Scrub + pair.next = m_freePair; + pair.proxy1 = null; + pair.proxy2 = null; + pair.userData = null; + pair.status = 0; + + m_freePair = pair; + --m_pairCount; + return userData; + } + + private function Find(proxy1:b2Proxy, proxy2:b2Proxy):b2Pair{ + + return proxy1.pairs[proxy2]; + } + + private function ValidateBuffer() : void{ + // DEBUG + } + + private function ValidateTable() : void{ + // DEBUG + } + +//public: + private var m_broadPhase:b2BroadPhase; + b2internal var m_pairs:Array; + private var m_freePair:b2Pair; + b2internal var m_pairCount:int; + + private var m_pairBuffer:Array; + private var m_pairBufferCount:int; + +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2Point.as b/srclib/Box2D/Collision/b2Point.as new file mode 100644 index 00000000..34df23f9 --- /dev/null +++ b/srclib/Box2D/Collision/b2Point.as @@ -0,0 +1,48 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + +use namespace b2internal; + +// This is used for polygon-vs-circle distance. +/** +* @private +*/ +public class b2Point +{ + public function b2Point() {} + + public function Support(xf:b2Transform, vX:Number, vY:Number) : b2Vec2 + { + return p; + } + + public function GetFirstVertex(xf:b2Transform) : b2Vec2 + { + return p; + } + + public var p:b2Vec2 = new b2Vec2(); +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2Proxy.as b/srclib/Box2D/Collision/b2Proxy.as new file mode 100644 index 00000000..c9334bf0 --- /dev/null +++ b/srclib/Box2D/Collision/b2Proxy.as @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision{ + + import Box2D.Common.b2internal; + + import flash.utils.Dictionary; +use namespace b2internal; + +/** +* @private +*/ +public class b2Proxy{ + + public function b2Proxy() {} + + public function IsValid():Boolean { return overlapCount != b2BroadPhase.b2_invalid; } + + public var lowerBounds:Vector. = new Vector.(2); + public var upperBounds:Vector. = new Vector.(2); + public var overlapCount:uint; + public var timeStamp:uint; + + // Maps from the other b2Proxy to their mutual b2Pair. + public var pairs:Dictionary = new Dictionary(); + + public var next:b2Proxy; + + public var userData:* = null; +} + + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2RayCastInput.as b/srclib/Box2D/Collision/b2RayCastInput.as new file mode 100644 index 00000000..737947fa --- /dev/null +++ b/srclib/Box2D/Collision/b2RayCastInput.as @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2009 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * Specifies a segment for use with RayCast functions. + */ +package Box2D.Collision +{ + + import Box2D.Common.Math.b2Vec2; + + public class b2RayCastInput + { + function b2RayCastInput(p1:b2Vec2 = null, p2:b2Vec2 = null, maxFraction:Number = 1) + { + if (p1) + this.p1.SetV(p1); + if (p2) + this.p2.SetV(p2); + this.maxFraction = maxFraction; + } + /** + * The start point of the ray + */ + public var p1:b2Vec2 = new b2Vec2(); + /** + * The end point of the ray + */ + public var p2:b2Vec2 = new b2Vec2(); + /** + * Truncate the ray to reach up to this fraction from p1 to p2 + */ + public var maxFraction:Number; + } + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2RayCastOutput.as b/srclib/Box2D/Collision/b2RayCastOutput.as new file mode 100644 index 00000000..791b6a3c --- /dev/null +++ b/srclib/Box2D/Collision/b2RayCastOutput.as @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2009 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * Returns data on the collision between a ray and a shape. + */ +package Box2D.Collision +{ + + import Box2D.Common.Math.b2Vec2; + + public class b2RayCastOutput + { + + public function b2RayCastOutput() {} + + /** + * The normal at the point of collision + */ + public var normal:b2Vec2 = new b2Vec2(); + /** + * The fraction between p1 and p2 that the collision occurs at + */ + public var fraction:Number; + } + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2Segment.as b/srclib/Box2D/Collision/b2Segment.as new file mode 100644 index 00000000..b5c535b1 --- /dev/null +++ b/srclib/Box2D/Collision/b2Segment.as @@ -0,0 +1,159 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.*; + import Box2D.Common.Math.*; + +use namespace b2internal; + +/** +* A line in space between two given vertices. +*/ +public class b2Segment +{ + public function b2Segment() {} + + /** + * Ray cast against this segment with another segment + * @param xf the shape world transform. + * @param lambda returns the hit fraction. You can use this to compute the contact point + * p = (1 - lambda) * segment.p1 + lambda * segment.p2. + * @param normal returns the normal at the contact point. If there is no intersection, the normal + * is not set. + * @param segment defines the begin and end point of the ray cast. + * @param maxLambda a number typically in the range [0,1]. + * @return true if there was an intersection. + * @see Box2D.Collision.Shapes.b2Shape#TestSegment + */ + // Collision Detection in Interactive 3D Environments by Gino van den Bergen + // From Section 3.4.1 + // x = mu1 * p1 + mu2 * p2 + // mu1 + mu2 = 1 && mu1 >= 0 && mu2 >= 0 + // mu1 = 1 - mu2; + // x = (1 - mu2) * p1 + mu2 * p2 + // = p1 + mu2 * (p2 - p1) + // x = s + a * r (s := start, r := end - start) + // s + a * r = p1 + mu2 * d (d := p2 - p1) + // -a * r + mu2 * d = b (b := s - p1) + // [-r d] * [a; mu2] = b + // Cramer's rule: + // denom = det[-r d] + // a = det[b d] / denom + // mu2 = det[-r b] / denom + public function TestSegment(lambda:Array, // float pointer + normal:b2Vec2, // pointer + segment:b2Segment, + maxLambda:Number) : Boolean{ + //b2Vec2 s = segment.p1; + var s:b2Vec2 = segment.p1; + //b2Vec2 r = segment.p2 - s; + var rX:Number = segment.p2.x - s.x; + var rY:Number = segment.p2.y - s.y; + //b2Vec2 d = p2 - p1; + var dX:Number = p2.x - p1.x; + var dY:Number = p2.y - p1.y; + //b2Vec2 n = b2Cross(d, 1.0f); + var nX:Number = dY; + var nY:Number = -dX; + + var k_slop:Number = 100.0 * Number.MIN_VALUE; + //var denom:Number = -b2Dot(r, n); + var denom:Number = -(rX*nX + rY*nY); + + // Cull back facing collision and ignore parallel segments. + if (denom > k_slop) + { + // Does the segment intersect the infinite line associated with this segment? + //b2Vec2 b = s - p1; + var bX:Number = s.x - p1.x; + var bY:Number = s.y - p1.y; + //var a:Number = b2Dot(b, n); + var a:Number = (bX*nX + bY*nY); + + if (0.0 <= a && a <= maxLambda * denom) + { + var mu2:Number = -rX * bY + rY * bX; + + // Does the segment intersect this segment? + if (-k_slop * denom <= mu2 && mu2 <= denom * (1.0 + k_slop)) + { + a /= denom; + //n.Normalize(); + var nLen:Number = Math.sqrt(nX*nX + nY*nY); + nX /= nLen; + nY /= nLen; + //*lambda = a; + lambda[0] = a; + //*normal = n; + normal.Set(nX, nY); + return true; + } + } + } + + return false; + } + + /** + * Extends or clips the segment so that it's ends lie on the boundary of the AABB + */ + public function Extend(aabb:b2AABB) : void{ + ExtendForward(aabb); + ExtendBackward(aabb); + } + + /** + * @see Extend + */ + public function ExtendForward(aabb:b2AABB) : void{ + var dX:Number = p2.x-p1.x; + var dY:Number = p2.y-p1.y; + + var lambda:Number = Math.min( dX>0?(aabb.upperBound.x-p1.x)/dX: dX<0?(aabb.lowerBound.x-p1.x)/dX:Number.POSITIVE_INFINITY, + dY>0?(aabb.upperBound.y-p1.y)/dY: dY<0?(aabb.lowerBound.y-p1.y)/dY:Number.POSITIVE_INFINITY); + + p2.x = p1.x + dX * lambda; + p2.y = p1.y + dY * lambda; + + } + + /** + * @see Extend + */ + public function ExtendBackward(aabb:b2AABB) : void{ + var dX:Number = -p2.x+p1.x; + var dY:Number = -p2.y+p1.y; + + var lambda:Number = Math.min( dX>0?(aabb.upperBound.x-p2.x)/dX: dX<0?(aabb.lowerBound.x-p2.x)/dX:Number.POSITIVE_INFINITY, + dY>0?(aabb.upperBound.y-p2.y)/dY: dY<0?(aabb.lowerBound.y-p2.y)/dY:Number.POSITIVE_INFINITY); + + p1.x = p2.x + dX * lambda; + p1.y = p2.y + dY * lambda; + + } + + /** The starting point */ + public var p1:b2Vec2 = new b2Vec2(); + /** The ending point */ + public var p2:b2Vec2 = new b2Vec2(); +}; + + +} diff --git a/srclib/Box2D/Collision/b2SeparationFunction.as b/srclib/Box2D/Collision/b2SeparationFunction.as new file mode 100644 index 00000000..54e96320 --- /dev/null +++ b/srclib/Box2D/Collision/b2SeparationFunction.as @@ -0,0 +1,326 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision +{ + + import Box2D.Common.*; + import Box2D.Common.Math.*; + + + +internal class b2SeparationFunction +{ + public function b2SeparationFunction() {} + + //enum Type + public static const e_points:int = 0x01; + public static const e_faceA:int = 0x02; + public static const e_faceB:int = 0x04; + + public function Initialize(cache:b2SimplexCache, + proxyA:b2DistanceProxy, transformA:b2Transform, + proxyB:b2DistanceProxy, transformB:b2Transform):void + { + m_proxyA = proxyA; + m_proxyB = proxyB; + var count:int = cache.count; + b2Settings.b2Assert(0 < count && count < 3); + + var localPointA:b2Vec2; + var localPointA1:b2Vec2; + var localPointA2:b2Vec2; + var localPointB:b2Vec2; + var localPointB1:b2Vec2; + var localPointB2:b2Vec2; + var pointAX:Number; + var pointAY:Number; + var pointBX:Number; + var pointBY:Number; + var normalX:Number; + var normalY:Number; + var tMat:b2Mat22; + var tVec:b2Vec2; + var s:Number; + var sgn:Number; + + if (count == 1) + { + m_type = e_points; + localPointA = m_proxyA.GetVertex(cache.indexA[0]); + localPointB = m_proxyB.GetVertex(cache.indexB[0]); + //pointA = b2Math.b2MulX(transformA, localPointA); + tVec = localPointA; + tMat = transformA.R; + pointAX = transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y) + pointAY = transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y) + //pointB = b2Math.b2MulX(transformB, localPointB); + tVec = localPointB; + tMat = transformB.R; + pointBX = transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y) + pointBY = transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y) + //m_axis = b2Math.SubtractVV(pointB, pointA); + m_axis.x = pointBX - pointAX; + m_axis.y = pointBY - pointAY; + m_axis.Normalize(); + } + else if (cache.indexB[0] == cache.indexB[1]) + { + // Two points on A and one on B + m_type = e_faceA; + localPointA1 = m_proxyA.GetVertex(cache.indexA[0]); + localPointA2 = m_proxyA.GetVertex(cache.indexA[1]); + localPointB = m_proxyB.GetVertex(cache.indexB[0]); + m_localPoint.x = 0.5 * (localPointA1.x + localPointA2.x); + m_localPoint.y = 0.5 * (localPointA1.y + localPointA2.y); + m_axis = b2Math.CrossVF(b2Math.SubtractVV(localPointA2, localPointA1), 1.0); + m_axis.Normalize(); + + //normal = b2Math.b2MulMV(transformA.R, m_axis); + tVec = m_axis; + tMat = transformA.R; + normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //pointA = b2Math.b2MulX(transformA, m_localPoint); + tVec = m_localPoint; + tMat = transformA.R; + pointAX = transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + pointAY = transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //pointB = b2Math.b2MulX(transformB, localPointB); + tVec = localPointB; + tMat = transformB.R; + pointBX = transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + pointBY = transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + //float32 s = b2Dot(pointB - pointA, normal); + s = (pointBX - pointAX) * normalX + (pointBY - pointAY) * normalY; + if (s < 0.0) + { + m_axis.NegativeSelf(); + } + } + else if (cache.indexA[0] == cache.indexA[0]) + { + // Two points on B and one on A + m_type = e_faceB; + localPointB1 = m_proxyB.GetVertex(cache.indexB[0]); + localPointB2 = m_proxyB.GetVertex(cache.indexB[1]); + localPointA = m_proxyA.GetVertex(cache.indexA[0]); + m_localPoint.x = 0.5 * (localPointB1.x + localPointB2.x); + m_localPoint.y = 0.5 * (localPointB1.y + localPointB2.y); + m_axis = b2Math.CrossVF(b2Math.SubtractVV(localPointB2, localPointB1), 1.0); + m_axis.Normalize(); + + //normal = b2Math.b2MulMV(transformB.R, m_axis); + tVec = m_axis; + tMat = transformB.R; + normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //pointB = b2Math.b2MulX(transformB, m_localPoint); + tVec = m_localPoint; + tMat = transformB.R; + pointBX = transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + pointBY = transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //pointA = b2Math.b2MulX(transformA, localPointA); + tVec = localPointA; + tMat = transformA.R; + pointAX = transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + pointAY = transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + //float32 s = b2Dot(pointA - pointB, normal); + s = (pointAX - pointBX) * normalX + (pointAY - pointBY) * normalY; + if (s < 0.0) + { + m_axis.NegativeSelf(); + } + } + else + { + // Two points on B and two points on A. + // The faces are parallel. + localPointA1 = m_proxyA.GetVertex(cache.indexA[0]); + localPointA2 = m_proxyA.GetVertex(cache.indexA[1]); + localPointB1 = m_proxyB.GetVertex(cache.indexB[0]); + localPointB2 = m_proxyB.GetVertex(cache.indexB[1]); + + var pA:b2Vec2 = b2Math.MulX(transformA, localPointA); + var dA:b2Vec2 = b2Math.MulMV(transformA.R, b2Math.SubtractVV(localPointA2, localPointA1)); + var pB:b2Vec2 = b2Math.MulX(transformB, localPointB); + var dB:b2Vec2 = b2Math.MulMV(transformB.R, b2Math.SubtractVV(localPointB2, localPointB1)); + + var a:Number = dA.x * dA.x + dA.y * dA.y; + var e:Number = dB.x * dB.x + dB.y * dB.y; + var r:b2Vec2 = b2Math.SubtractVV(dB, dA); + var c:Number = dA.x * r.x + dA.y * r.y; + var f:Number = dB.x * r.x + dB.y * r.y; + + var b:Number = dA.x * dB.x + dA.y * dB.y; + var denom:Number = a * e-b * b; + + s = 0.0; + if (denom != 0.0) + { + s = b2Math.Clamp((b * f - c * e) / denom, 0.0, 1.0); + } + + var t:Number = (b * s + f) / e; + if (t < 0.0) + { + t = 0.0; + s = b2Math.Clamp((b - c) / a, 0.0, 1.0); + } + + //b2Vec2 localPointA = localPointA1 + s * (localPointA2 - localPointA1); + localPointA = new b2Vec2(); + localPointA.x = localPointA1.x + s * (localPointA2.x - localPointA1.x); + localPointA.y = localPointA1.y + s * (localPointA2.y - localPointA1.y); + //b2Vec2 localPointB = localPointB1 + s * (localPointB2 - localPointB1); + localPointB = new b2Vec2(); + localPointB.x = localPointB1.x + s * (localPointB2.x - localPointB1.x); + localPointB.y = localPointB1.y + s * (localPointB2.y - localPointB1.y); + + if (s == 0.0 || s == 1.0) + { + m_type = e_faceB; + m_axis = b2Math.CrossVF(b2Math.SubtractVV(localPointB2, localPointB1), 1.0); + m_axis.Normalize(); + + m_localPoint = localPointB; + + //normal = b2Math.b2MulMV(transformB.R, m_axis); + tVec = m_axis; + tMat = transformB.R; + normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //pointB = b2Math.b2MulX(transformB, m_localPoint); + tVec = m_localPoint; + tMat = transformB.R; + pointBX = transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + pointBY = transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //pointA = b2Math.b2MulX(transformA, localPointA); + tVec = localPointA; + tMat = transformA.R; + pointAX = transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + pointAY = transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + //float32 sgn = b2Dot(pointA - pointB, normal); + sgn = (pointAX - pointBX) * normalX + (pointAY - pointBY) * normalY; + if (s < 0.0) + { + m_axis.NegativeSelf(); + } + } + else + { + m_type = e_faceA; + m_axis = b2Math.CrossVF(b2Math.SubtractVV(localPointA2, localPointA1), 1.0); + + m_localPoint = localPointA; + + //normal = b2Math.b2MulMV(transformA.R, m_axis); + tVec = m_axis; + tMat = transformA.R; + normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //pointA = b2Math.b2MulX(transformA, m_localPoint); + tVec = m_localPoint; + tMat = transformA.R; + pointAX = transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + pointAY = transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //pointB = b2Math.b2MulX(transformB, localPointB); + tVec = localPointB; + tMat = transformB.R; + pointBX = transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + pointBY = transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + //float32 sgn = b2Dot(pointB - pointA, normal); + sgn = (pointBX - pointAX) * normalX + (pointBY - pointAY) * normalY; + if (s < 0.0) + { + m_axis.NegativeSelf(); + } + } + } + } + + public function Evaluate(transformA:b2Transform, transformB:b2Transform):Number + { + var axisA:b2Vec2; + var axisB:b2Vec2; + var localPointA:b2Vec2 + var localPointB:b2Vec2; + var pointA:b2Vec2; + var pointB:b2Vec2; + var seperation:Number; + var normal:b2Vec2; + switch(m_type) + { + case e_points: + { + axisA = b2Math.MulTMV(transformA.R, m_axis); + axisB = b2Math.MulTMV(transformB.R, m_axis.GetNegative()); + localPointA = m_proxyA.GetSupportVertex(axisA); + localPointB = m_proxyB.GetSupportVertex(axisB); + pointA = b2Math.MulX(transformA, localPointA); + pointB = b2Math.MulX(transformB, localPointB); + //float32 separation = b2Dot(pointB - pointA, m_axis); + seperation = (pointB.x - pointA.x) * m_axis.x + (pointB.y - pointA.y) * m_axis.y; + return seperation; + } + case e_faceA: + { + normal = b2Math.MulMV(transformA.R, m_axis); + pointA = b2Math.MulX(transformA, m_localPoint); + + axisB = b2Math.MulTMV(transformB.R, normal.GetNegative()); + + localPointB = m_proxyB.GetSupportVertex(axisB); + pointB = b2Math.MulX(transformB, localPointB); + + //float32 separation = b2Dot(pointB - pointA, normal); + seperation = (pointB.x - pointA.x) * normal.x + (pointB.y - pointA.y) * normal.y; + return seperation; + } + case e_faceB: + { + normal = b2Math.MulMV(transformB.R, m_axis); + pointB = b2Math.MulX(transformB, m_localPoint); + + axisA = b2Math.MulTMV(transformA.R, normal.GetNegative()); + + localPointA = m_proxyA.GetSupportVertex(axisA); + pointA = b2Math.MulX(transformA, localPointA); + + //float32 separation = b2Dot(pointA - pointB, normal); + seperation = (pointA.x - pointB.x) * normal.x + (pointA.y - pointB.y) * normal.y; + return seperation; + } + default: + b2Settings.b2Assert(false); + return 0.0; + } + } + + public var m_proxyA:b2DistanceProxy; + public var m_proxyB:b2DistanceProxy; + public var m_type:int; + public var m_localPoint:b2Vec2 = new b2Vec2(); + public var m_axis:b2Vec2 = new b2Vec2(); +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2Simplex.as b/srclib/Box2D/Collision/b2Simplex.as new file mode 100644 index 00000000..9ca3f0d7 --- /dev/null +++ b/srclib/Box2D/Collision/b2Simplex.as @@ -0,0 +1,368 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision +{ + + import Box2D.Common.*; + import Box2D.Common.Math.*; + +internal class b2Simplex +{ + +public function b2Simplex() +{ + m_vertices[0] = m_v1; + m_vertices[1] = m_v2; + m_vertices[2] = m_v3; +} + +public function ReadCache(cache:b2SimplexCache, + proxyA:b2DistanceProxy, transformA:b2Transform, + proxyB:b2DistanceProxy, transformB:b2Transform):void +{ + b2Settings.b2Assert(0 <= cache.count && cache.count <= 3); + + var wALocal:b2Vec2; + var wBLocal:b2Vec2; + + // Copy data from cache. + m_count = cache.count; + var vertices:Vector. = m_vertices; + for (var i:int = 0; i < m_count; i++) + { + var v:b2SimplexVertex = vertices[i]; + v.indexA = cache.indexA[i]; + v.indexB = cache.indexB[i]; + wALocal = proxyA.GetVertex(v.indexA); + wBLocal = proxyB.GetVertex(v.indexB); + v.wA = b2Math.MulX(transformA, wALocal); + v.wB = b2Math.MulX(transformB, wBLocal); + v.w = b2Math.SubtractVV(v.wB, v.wA); + v.a = 0; + } + + // Compute the new simplex metric, if it substantially different than + // old metric then flush the simplex + if (m_count > 1) + { + var metric1:Number = cache.metric; + var metric2:Number = GetMetric(); + if (metric2 < .5 * metric1 || 2.0 * metric1 < metric2 || metric2 < Number.MIN_VALUE) + { + // Reset the simplex + m_count = 0; + } + } + + // If the cache is empty or invalid + if (m_count == 0) + { + v = vertices[0]; + v.indexA = 0; + v.indexB = 0; + wALocal = proxyA.GetVertex(0); + wBLocal = proxyB.GetVertex(0); + v.wA = b2Math.MulX(transformA, wALocal); + v.wB = b2Math.MulX(transformB, wBLocal); + v.w = b2Math.SubtractVV(v.wB, v.wA); + m_count = 1; + } +} + +public function WriteCache(cache:b2SimplexCache):void +{ + cache.metric = GetMetric(); + cache.count = uint(m_count); + var vertices:Vector. = m_vertices; + for (var i:int = 0; i < m_count; i++) + { + cache.indexA[i] = uint(vertices[i].indexA); + cache.indexB[i] = uint(vertices[i].indexB); + } +} + +public function GetSearchDirection():b2Vec2 +{ + switch(m_count) + { + case 1: + return m_v1.w.GetNegative(); + + case 2: + { + var e12:b2Vec2 = b2Math.SubtractVV(m_v2.w, m_v1.w); + var sgn:Number = b2Math.CrossVV(e12, m_v1.w.GetNegative()); + if (sgn > 0.0) + { + // Origin is left of e12. + return b2Math.CrossFV(1.0, e12); + }else { + // Origin is right of e12. + return b2Math.CrossVF(e12, 1.0); + } + } + default: + b2Settings.b2Assert(false); + return new b2Vec2(); + } +} + +public function GetClosestPoint():b2Vec2 +{ + switch(m_count) + { + case 0: + b2Settings.b2Assert(false); + return new b2Vec2(); + case 1: + return m_v1.w; + case 2: + return new b2Vec2( + m_v1.a * m_v1.w.x + m_v2.a * m_v2.w.x, + m_v1.a * m_v1.w.y + m_v2.a * m_v2.w.y); + default: + b2Settings.b2Assert(false); + return new b2Vec2(); + } +} + +public function GetWitnessPoints(pA:b2Vec2, pB:b2Vec2):void +{ + switch(m_count) + { + case 0: + b2Settings.b2Assert(false); + break; + case 1: + pA.SetV(m_v1.wA); + pB.SetV(m_v1.wB); + break; + case 2: + pA.x = m_v1.a * m_v1.wA.x + m_v2.a * m_v2.wA.x; + pA.y = m_v1.a * m_v1.wA.y + m_v2.a * m_v2.wA.y; + pB.x = m_v1.a * m_v1.wB.x + m_v2.a * m_v2.wB.x; + pB.y = m_v1.a * m_v1.wB.y + m_v2.a * m_v2.wB.y; + break; + case 3: + pB.x = pA.x = m_v1.a * m_v1.wA.x + m_v2.a * m_v2.wA.x + m_v3.a * m_v3.wA.x; + pB.y = pA.y = m_v1.a * m_v1.wA.y + m_v2.a * m_v2.wA.y + m_v3.a * m_v3.wA.y; + break; + default: + b2Settings.b2Assert(false); + break; + } +} + +public function GetMetric():Number +{ + switch (m_count) + { + case 0: + b2Settings.b2Assert(false); + return 0.0; + + case 1: + return 0.0; + + case 2: + return b2Math.SubtractVV(m_v1.w, m_v2.w).Length(); + + case 3: + return b2Math.CrossVV(b2Math.SubtractVV(m_v2.w, m_v1.w),b2Math.SubtractVV(m_v3.w, m_v1.w)); + + default: + b2Settings.b2Assert(false); + return 0.0; + } +} + +// Solve a line segment using barycentric coordinates. +// +// p = a1 * w1 + a2 * w2 +// a1 + a2 = 1 +// +// The vector from the origin to the closest point on the line is +// perpendicular to the line. +// e12 = w2 - w1 +// dot(p, e) = 0 +// a1 * dot(w1, e) + a2 * dot(w2, e) = 0 +// +// 2-by-2 linear system +// [1 1 ][a1] = [1] +// [w1.e12 w2.e12][a2] = [0] +// +// Define +// d12_1 = dot(w2, e12) +// d12_2 = -dot(w1, e12) +// d12 = d12_1 + d12_2 +// +// Solution +// a1 = d12_1 / d12 +// a2 = d12_2 / d12 +public function Solve2():void +{ + var w1:b2Vec2 = m_v1.w; + var w2:b2Vec2 = m_v2.w; + var e12:b2Vec2 = b2Math.SubtractVV(w2, w1); + + // w1 region + var d12_2:Number = -(w1.x * e12.x + w1.y * e12.y); + if (d12_2 <= 0.0) + { + // a2 <= 0, so we clamp it to 0 + m_v1.a = 1.0; + m_count = 1; + return; + } + + // w2 region + var d12_1:Number = (w2.x * e12.x + w2.y * e12.y); + if (d12_1 <= 0.0) + { + // a1 <= 0, so we clamp it to 0 + m_v2.a = 1.0; + m_count = 1; + m_v1.Set(m_v2); + return; + } + + // Must be in e12 region. + var inv_d12:Number = 1.0 / (d12_1 + d12_2); + m_v1.a = d12_1 * inv_d12; + m_v2.a = d12_2 * inv_d12; + m_count = 2; +} + +public function Solve3():void +{ + var w1:b2Vec2 = m_v1.w; + var w2:b2Vec2 = m_v2.w; + var w3:b2Vec2 = m_v3.w; + + // Edge12 + // [1 1 ][a1] = [1] + // [w1.e12 w2.e12][a2] = [0] + // a3 = 0 + var e12:b2Vec2 = b2Math.SubtractVV(w2, w1); + var w1e12:Number = b2Math.Dot(w1, e12); + var w2e12:Number = b2Math.Dot(w2, e12); + var d12_1:Number = w2e12; + var d12_2:Number = -w1e12; + + // Edge13 + // [1 1 ][a1] = [1] + // [w1.e13 w3.e13][a3] = [0] + // a2 = 0 + var e13:b2Vec2 = b2Math.SubtractVV(w3, w1); + var w1e13:Number = b2Math.Dot(w1, e13); + var w3e13:Number = b2Math.Dot(w3, e13); + var d13_1:Number = w3e13; + var d13_2:Number = -w1e13; + + // Edge23 + // [1 1 ][a2] = [1] + // [w2.e23 w3.e23][a3] = [0] + // a1 = 0 + var e23:b2Vec2 = b2Math.SubtractVV(w3, w2); + var w2e23:Number = b2Math.Dot(w2, e23); + var w3e23:Number = b2Math.Dot(w3, e23); + var d23_1:Number = w3e23; + var d23_2:Number = -w2e23; + + // Triangle123 + var n123:Number = b2Math.CrossVV(e12, e13); + + var d123_1:Number = n123 * b2Math.CrossVV(w2, w3); + var d123_2:Number = n123 * b2Math.CrossVV(w3, w1); + var d123_3:Number = n123 * b2Math.CrossVV(w1, w2); + + // w1 region + if (d12_2 <= 0.0 && d13_2 <= 0.0) + { + m_v1.a = 1.0; + m_count = 1; + return; + } + + // e12 + if (d12_1 > 0.0 && d12_2 > 0.0 && d123_3 <= 0.0) + { + var inv_d12:Number = 1.0 / (d12_1 + d12_2); + m_v1.a = d12_1 * inv_d12; + m_v2.a = d12_2 * inv_d12; + m_count = 2; + return; + } + + // e13 + if (d13_1 > 0.0 && d13_2 > 0.0 && d123_2 <= 0.0) + { + var inv_d13:Number = 1.0 / (d13_1 + d13_2); + m_v1.a = d13_1 * inv_d13; + m_v3.a = d13_2 * inv_d13; + m_count = 2; + m_v2.Set(m_v3); + return; + } + + // w2 region + if (d12_1 <= 0.0 && d23_2 <= 0.0) + { + m_v2.a = 1.0; + m_count = 1; + m_v1.Set(m_v2); + return; + } + + // w3 region + if (d13_1 <= 0.0 && d23_1 <= 0.0) + { + m_v3.a = 1.0; + m_count = 1; + m_v1.Set(m_v3); + return; + } + + // e23 + if (d23_1 > 0.0 && d23_2 > 0.0 && d123_1 <= 0.0) + { + var inv_d23:Number = 1.0 / (d23_1 + d23_2); + m_v2.a = d23_1 * inv_d23; + m_v3.a = d23_2 * inv_d23; + m_count = 2; + m_v1.Set(m_v3); + return; + } + + // Must be in triangle123 + var inv_d123:Number = 1.0 / (d123_1 + d123_2 + d123_3); + m_v1.a = d123_1 * inv_d123; + m_v2.a = d123_2 * inv_d123; + m_v3.a = d123_3 * inv_d123; + m_count = 3; +} + +public var m_v1:b2SimplexVertex = new b2SimplexVertex(); +public var m_v2:b2SimplexVertex = new b2SimplexVertex(); +public var m_v3:b2SimplexVertex = new b2SimplexVertex(); +public var m_vertices:Vector. = new Vector.(3); +public var m_count:int; +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2SimplexCache.as b/srclib/Box2D/Collision/b2SimplexCache.as new file mode 100644 index 00000000..5733d91d --- /dev/null +++ b/srclib/Box2D/Collision/b2SimplexCache.as @@ -0,0 +1,40 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision +{ + + /** + * Used to warm start b2Distance. + * Set count to zero on first call. + */ + public class b2SimplexCache + { + + public function b2SimplexCache() {} + + /** Length or area */ + public var metric:Number; + public var count:uint; + /** Vertices on shape a */ + public var indexA:Vector. = new Vector.(3); + /** Vertices on shape b */ + public var indexB:Vector. = new Vector.(3); +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2SimplexVertex.as b/srclib/Box2D/Collision/b2SimplexVertex.as new file mode 100644 index 00000000..7c708822 --- /dev/null +++ b/srclib/Box2D/Collision/b2SimplexVertex.as @@ -0,0 +1,47 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision +{ + + import Box2D.Common.Math.*; + + +internal class b2SimplexVertex +{ + public function b2SimplexVertex() {} + + public function Set(other:b2SimplexVertex):void + { + wA.SetV(other.wA); + wB.SetV(other.wB); + w.SetV(other.w); + a = other.a; + indexA = other.indexA; + indexB = other.indexB; + } + + public var wA:b2Vec2; // support point in proxyA + public var wB:b2Vec2; // support point in proxyB + public var w:b2Vec2; // wB - wA + public var a:Number; // barycentric coordinate for closest point + public var indexA:int; // wA index + public var indexB:int; // wB index +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2TOIInput.as b/srclib/Box2D/Collision/b2TOIInput.as new file mode 100644 index 00000000..bc9ad1c2 --- /dev/null +++ b/srclib/Box2D/Collision/b2TOIInput.as @@ -0,0 +1,40 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision +{ + + import Box2D.Common.Math.b2Sweep; + + /** + * Inpute parameters for b2TimeOfImpact + */ + public class b2TOIInput + { + + public function b2TOIInput() {} + + public var proxyA:b2DistanceProxy = new b2DistanceProxy(); + public var proxyB:b2DistanceProxy = new b2DistanceProxy(); + public var sweepA:b2Sweep = new b2Sweep(); + public var sweepB:b2Sweep = new b2Sweep(); + public var tolerance:Number; + + } + +} \ No newline at end of file diff --git a/srclib/Box2D/Collision/b2TimeOfImpact.as b/srclib/Box2D/Collision/b2TimeOfImpact.as new file mode 100644 index 00000000..61f30894 --- /dev/null +++ b/srclib/Box2D/Collision/b2TimeOfImpact.as @@ -0,0 +1,242 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision { + + import Box2D.Common.*; + import Box2D.Common.Math.*; + +use namespace b2internal; + + +/** +* @private +*/ +public class b2TimeOfImpact +{ + + private static var b2_toiCalls:int = 0; + private static var b2_toiIters:int = 0; + private static var b2_toiMaxIters:int = 0; + private static var b2_toiRootIters:int = 0; + private static var b2_toiMaxRootIters:int = 0; + + private static var s_cache:b2SimplexCache = new b2SimplexCache(); + private static var s_distanceInput:b2DistanceInput = new b2DistanceInput(); + private static var s_xfA:b2Transform = new b2Transform(); + private static var s_xfB:b2Transform = new b2Transform(); + private static var s_fcn:b2SeparationFunction = new b2SeparationFunction(); + private static var s_distanceOutput:b2DistanceOutput = new b2DistanceOutput(); + public static function TimeOfImpact(input:b2TOIInput):Number + { + ++b2_toiCalls; + + var proxyA:b2DistanceProxy = input.proxyA; + var proxyB:b2DistanceProxy = input.proxyB; + + var sweepA:b2Sweep = input.sweepA; + var sweepB:b2Sweep = input.sweepB; + + b2Settings.b2Assert(sweepA.t0 == sweepB.t0); + b2Settings.b2Assert(1.0 - sweepA.t0 > Number.MIN_VALUE); + + var radius:Number = proxyA.m_radius + proxyB.m_radius; + var tolerance:Number = input.tolerance; + + var alpha:Number = 0.0; + + const k_maxIterations:int = 1000; //TODO_ERIN b2Settings + var iter:int = 0; + var target:Number = 0.0; + + // Prepare input for distance query. + s_cache.count = 0; + s_distanceInput.useRadii = false; + + for (;; ) + { + sweepA.GetTransform(s_xfA, alpha); + sweepB.GetTransform(s_xfB, alpha); + + // Get the distance between shapes + s_distanceInput.proxyA = proxyA; + s_distanceInput.proxyB = proxyB; + s_distanceInput.transformA = s_xfA; + s_distanceInput.transformB = s_xfB; + + b2Distance.Distance(s_distanceOutput, s_cache, s_distanceInput); + + if (s_distanceOutput.distance <= 0.0) + { + alpha = 1.0; + break; + } + + s_fcn.Initialize(s_cache, proxyA, s_xfA, proxyB, s_xfB); + + var separation:Number = s_fcn.Evaluate(s_xfA, s_xfB); + if (separation <= 0.0) + { + alpha = 1.0; + break; + } + + if (iter == 0) + { + // Compute a reasonable target distance to give some breathing room + // for conservative advancement. We take advantage of the shape radii + // to create additional clearance + if (separation > radius) + { + target = b2Math.Max(radius - tolerance, 0.75 * radius); + } + else + { + target = b2Math.Max(separation - tolerance, 0.02 * radius); + } + } + + if (separation - target < 0.5 * tolerance) + { + if (iter == 0) + { + alpha = 1.0; + break; + } + break; + } + +//#if 0 + // Dump the curve seen by the root finder + //{ + //const N:int = 100; + //var dx:Number = 1.0 / N; + //var xs:Vector. = new Array(N + 1); + //var fs:Vector. = new Array(N + 1); + // + //var x:Number = 0.0; + //for (var i:int = 0; i <= N; i++) + //{ + //sweepA.GetTransform(xfA, x); + //sweepB.GetTransform(xfB, x); + //var f:Number = fcn.Evaluate(xfA, xfB) - target; + // + //trace(x, f); + //xs[i] = x; + //fx[i] = f' + // + //x += dx; + //} + //} +//#endif + // Compute 1D root of f(x) - target = 0 + var newAlpha:Number = alpha; + { + var x1:Number = alpha; + var x2:Number = 1.0; + + var f1:Number = separation; + + sweepA.GetTransform(s_xfA, x2); + sweepB.GetTransform(s_xfB, x2); + + var f2:Number = s_fcn.Evaluate(s_xfA, s_xfB); + + // If intervals don't overlap at t2, then we are done + if (f2 >= target) + { + alpha = 1.0; + break; + } + + // Determine when intervals intersect + var rootIterCount:int = 0; + for (;; ) + { + // Use a mis of the secand rule and bisection + var x:Number; + if (rootIterCount & 1) + { + // Secant rule to improve convergence + x = x1 + (target - f1) * (x2 - x1) / (f2 - f1); + } + else + { + // Bisection to guarantee progress + x = 0.5 * (x1 + x2); + } + + sweepA.GetTransform(s_xfA, x); + sweepB.GetTransform(s_xfB, x); + + var f:Number = s_fcn.Evaluate(s_xfA, s_xfB); + + if (b2Math.Abs(f - target) < 0.025 * tolerance) + { + newAlpha = x; + break; + } + + // Ensure we continue to bracket the root + if (f > target) + { + x1 = x; + f1 = f; + } + else + { + x2 = x; + f2 = f; + } + + ++rootIterCount; + ++b2_toiRootIters; + if (rootIterCount == 50) + { + break; + } + } + + b2_toiMaxRootIters = b2Math.Max(b2_toiMaxRootIters, rootIterCount); + } + + // Ensure significant advancement + if (newAlpha < (1.0 + 100.0 * Number.MIN_VALUE) * alpha) + { + break; + } + + alpha = newAlpha; + + iter++; + ++b2_toiIters; + + if (iter == k_maxIterations) + { + break; + } + } + + b2_toiMaxIters = b2Math.Max(b2_toiMaxIters, iter); + + return alpha; + } + +} + +} diff --git a/srclib/Box2D/Collision/b2WorldManifold.as b/srclib/Box2D/Collision/b2WorldManifold.as new file mode 100644 index 00000000..75c41a7b --- /dev/null +++ b/srclib/Box2D/Collision/b2WorldManifold.as @@ -0,0 +1,185 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Collision +{ + + import Box2D.Common.*; + import Box2D.Common.Math.*; +use namespace b2internal; + +/** + * This is used to compute the current state of a contact manifold. + */ +public class b2WorldManifold +{ + public function b2WorldManifold() + { + m_points = new Vector.(b2Settings.b2_maxManifoldPoints) + for (var i:int = 0; i < b2Settings.b2_maxManifoldPoints; i++) + { + m_points[i] = new b2Vec2(); + } + } + /** + * Evaluate the manifold with supplied transforms. This assumes + * modest motion from the original state. This does not change the + * point count, impulses, etc. The radii must come from the shapes + * that generated the manifold. + */ + public function Initialize(manifold:b2Manifold, + xfA:b2Transform, radiusA:Number, + xfB:b2Transform, radiusB:Number):void + { + if (manifold.m_pointCount == 0) + { + return; + } + + var i:int; + var tVec:b2Vec2; + var tMat:b2Mat22; + var normalX:Number; + var normalY:Number; + var planePointX:Number; + var planePointY:Number; + var clipPointX:Number; + var clipPointY:Number; + + switch(manifold.m_type) + { + case b2Manifold.e_circles: + { + //var pointA:b2Vec2 = b2Math.b2MulX(xfA, manifold.m_localPoint); + tMat = xfA.R; + tVec = manifold.m_localPoint; + var pointAX:Number = xfA.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + var pointAY:Number = xfA.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //var pointB:b2Vec2 = b2Math.b2MulX(xfB, manifold.m_points[0].m_localPoint); + tMat = xfB.R; + tVec = manifold.m_points[0].m_localPoint; + var pointBX:Number = xfB.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + var pointBY:Number = xfB.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + + var dX:Number = pointBX - pointAX; + var dY:Number = pointBY - pointAY; + var d2:Number = dX * dX + dY * dY; + if (d2 > Number.MIN_VALUE * Number.MIN_VALUE) + { + var d:Number = Math.sqrt(d2); + m_normal.x = dX/d; + m_normal.y = dY/d; + }else { + m_normal.x = 1; + m_normal.y = 0; + } + + //b2Vec2 cA = pointA + radiusA * m_normal; + var cAX:Number = pointAX + radiusA * m_normal.x; + var cAY:Number = pointAY + radiusA * m_normal.y; + //b2Vec2 cB = pointB - radiusB * m_normal; + var cBX:Number = pointBX - radiusB * m_normal.x; + var cBY:Number = pointBY - radiusB * m_normal.y; + m_points[0].x = 0.5 * (cAX + cBX); + m_points[0].y = 0.5 * (cAY + cBY); + } + break; + case b2Manifold.e_faceA: + { + //normal = b2Math.b2MulMV(xfA.R, manifold.m_localPlaneNormal); + tMat = xfA.R; + tVec = manifold.m_localPlaneNormal; + normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + + //planePoint = b2Math.b2MulX(xfA, manifold.m_localPoint); + tMat = xfA.R; + tVec = manifold.m_localPoint; + planePointX = xfA.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + planePointY = xfA.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + + // Ensure normal points from A to B + m_normal.x = normalX; + m_normal.y = normalY; + for (i = 0; i < manifold.m_pointCount; i++) + { + //clipPoint = b2Math.b2MulX(xfB, manifold.m_points[i].m_localPoint); + tMat = xfB.R; + tVec = manifold.m_points[i].m_localPoint; + clipPointX = xfB.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + clipPointY = xfB.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + + //b2Vec2 cA = clipPoint + (radiusA - b2Dot(clipPoint - planePoint, normal)) * normal; + //b2Vec2 cB = clipPoint - radiusB * normal; + //m_points[i] = 0.5f * (cA + cB); + m_points[i].x = clipPointX + 0.5 * (radiusA - (clipPointX - planePointX) * normalX - (clipPointY - planePointY) * normalY - radiusB ) * normalX; + m_points[i].y = clipPointY + 0.5 * (radiusA - (clipPointX - planePointX) * normalX - (clipPointY - planePointY) * normalY - radiusB ) * normalY; + + } + } + break; + case b2Manifold.e_faceB: + { + //normal = b2Math.b2MulMV(xfB.R, manifold.m_localPlaneNormal); + tMat = xfB.R; + tVec = manifold.m_localPlaneNormal; + normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + + //planePoint = b2Math.b2MulX(xfB, manifold.m_localPoint); + tMat = xfB.R; + tVec = manifold.m_localPoint; + planePointX = xfB.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + planePointY = xfB.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + + // Ensure normal points from A to B + m_normal.x = -normalX; + m_normal.y = -normalY; + for (i = 0; i < manifold.m_pointCount; i++) + { + //clipPoint = b2Math.b2MulX(xfA, manifold.m_points[i].m_localPoint); + tMat = xfA.R; + tVec = manifold.m_points[i].m_localPoint; + clipPointX = xfA.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + clipPointY = xfA.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + + //b2Vec2 cA = clipPoint - radiusA * normal; + //b2Vec2 cB = clipPoint + (radiusB - b2Dot(clipPoint - planePoint, normal)) * normal; + //m_points[i] = 0.5f * (cA + cB); + m_points[i].x = clipPointX + 0.5 * (radiusB - (clipPointX - planePointX) * normalX - (clipPointY - planePointY) * normalY - radiusA ) * normalX; + m_points[i].y = clipPointY + 0.5 * (radiusB - (clipPointX - planePointX) * normalX - (clipPointY - planePointY) * normalY - radiusA ) * normalY; + + } + } + break; + } + } + + /** + * world vector pointing from A to B + */ + public var m_normal:b2Vec2 = new b2Vec2(); // added by the CE team! + + /** + * world contact point (point of intersection) + */ + public var m_points:Vector.; // added by the CE team! + +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Common/Math/b2Mat22.as b/srclib/Box2D/Common/Math/b2Mat22.as new file mode 100644 index 00000000..a7d7f048 --- /dev/null +++ b/srclib/Box2D/Common/Math/b2Mat22.as @@ -0,0 +1,149 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Common.Math{ + + + + +/** +* A 2-by-2 matrix. Stored in column-major order. +*/ +public class b2Mat22 +{ + public function b2Mat22() + { + col1.x = col2.y = 1.0; + } + + public static function FromAngle(angle:Number):b2Mat22 + { + var mat:b2Mat22 = new b2Mat22(); + mat.Set(angle); + return mat; + } + + public static function FromVV(c1:b2Vec2, c2:b2Vec2):b2Mat22 + { + var mat:b2Mat22 = new b2Mat22(); + mat.SetVV(c1, c2); + return mat; + } + + public function Set(angle:Number) : void + { + var c:Number = Math.cos(angle); + var s:Number = Math.sin(angle); + col1.x = c; col2.x = -s; + col1.y = s; col2.y = c; + } + + public function SetVV(c1:b2Vec2, c2:b2Vec2) : void + { + col1.SetV(c1); + col2.SetV(c2); + } + + public function Copy():b2Mat22{ + var mat:b2Mat22 = new b2Mat22(); + mat.SetM(this); + return mat; + } + + public function SetM(m:b2Mat22) : void + { + col1.SetV(m.col1); + col2.SetV(m.col2); + } + + public function AddM(m:b2Mat22) : void + { + col1.x += m.col1.x; + col1.y += m.col1.y; + col2.x += m.col2.x; + col2.y += m.col2.y; + } + + public function SetIdentity() : void + { + col1.x = 1.0; col2.x = 0.0; + col1.y = 0.0; col2.y = 1.0; + } + + public function SetZero() : void + { + col1.x = 0.0; col2.x = 0.0; + col1.y = 0.0; col2.y = 0.0; + } + + public function GetAngle() :Number + { + return Math.atan2(col1.y, col1.x); + } + + /** + * Compute the inverse of this matrix, such that inv(A) * A = identity. + */ + public function GetInverse(out:b2Mat22):b2Mat22 + { + var a:Number = col1.x; + var b:Number = col2.x; + var c:Number = col1.y; + var d:Number = col2.y; + //var B:b2Mat22 = new b2Mat22(); + var det:Number = a * d - b * c; + if (det != 0.0) + { + det = 1.0 / det; + } + out.col1.x = det * d; out.col2.x = -det * b; + out.col1.y = -det * c; out.col2.y = det * a; + return out; + } + + // Solve A * x = b + public function Solve(out:b2Vec2, bX:Number, bY:Number):b2Vec2 + { + //float32 a11 = col1.x, a12 = col2.x, a21 = col1.y, a22 = col2.y; + var a11:Number = col1.x; + var a12:Number = col2.x; + var a21:Number = col1.y; + var a22:Number = col2.y; + //float32 det = a11 * a22 - a12 * a21; + var det:Number = a11 * a22 - a12 * a21; + if (det != 0.0) + { + det = 1.0 / det; + } + out.x = det * (a22 * bX - a12 * bY); + out.y = det * (a11 * bY - a21 * bX); + + return out; + } + + public function Abs() : void + { + col1.Abs(); + col2.Abs(); + } + + public var col1:b2Vec2 = new b2Vec2(); + public var col2:b2Vec2 = new b2Vec2(); +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Common/Math/b2Mat33.as b/srclib/Box2D/Common/Math/b2Mat33.as new file mode 100644 index 00000000..efc91085 --- /dev/null +++ b/srclib/Box2D/Common/Math/b2Mat33.as @@ -0,0 +1,150 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Common.Math{ + + + + +/** +* A 3-by-3 matrix. Stored in column-major order. +*/ +public class b2Mat33 +{ + public function b2Mat33(c1:b2Vec3=null, c2:b2Vec3=null, c3:b2Vec3=null) + { + if (!c1 && !c2 && !c3) + { + col1.SetZero(); + col2.SetZero(); + col3.SetZero(); + } + else + { + col1.SetV(c1); + col2.SetV(c2); + col3.SetV(c3); + } + } + + public function SetVVV(c1:b2Vec3, c2:b2Vec3, c3:b2Vec3) : void + { + col1.SetV(c1); + col2.SetV(c2); + col3.SetV(c3); + } + + public function Copy():b2Mat33{ + return new b2Mat33(col1, col2, col3); + } + + public function SetM(m:b2Mat33) : void + { + col1.SetV(m.col1); + col2.SetV(m.col2); + col3.SetV(m.col3); + } + + public function AddM(m:b2Mat33) : void + { + col1.x += m.col1.x; + col1.y += m.col1.y; + col1.z += m.col1.z; + col2.x += m.col2.x; + col2.y += m.col2.y; + col2.z += m.col2.z; + col3.x += m.col3.x; + col3.y += m.col3.y; + col3.z += m.col3.z; + } + + public function SetIdentity() : void + { + col1.x = 1.0; col2.x = 0.0; col3.x = 0.0; + col1.y = 0.0; col2.y = 1.0; col3.y = 0.0; + col1.z = 0.0; col2.z = 0.0; col3.z = 1.0; + } + + public function SetZero() : void + { + col1.x = 0.0; col2.x = 0.0; col3.x = 0.0; + col1.y = 0.0; col2.y = 0.0; col3.y = 0.0; + col1.z = 0.0; col2.z = 0.0; col3.z = 0.0; + } + + // Solve A * x = b + public function Solve22(out:b2Vec2, bX:Number, bY:Number):b2Vec2 + { + //float32 a11 = col1.x, a12 = col2.x, a21 = col1.y, a22 = col2.y; + var a11:Number = col1.x; + var a12:Number = col2.x; + var a21:Number = col1.y; + var a22:Number = col2.y; + //float32 det = a11 * a22 - a12 * a21; + var det:Number = a11 * a22 - a12 * a21; + if (det != 0.0) + { + det = 1.0 / det; + } + out.x = det * (a22 * bX - a12 * bY); + out.y = det * (a11 * bY - a21 * bX); + + return out; + } + + // Solve A * x = b + public function Solve33(out:b2Vec3, bX:Number, bY:Number, bZ:Number):b2Vec3 + { + var a11:Number = col1.x; + var a21:Number = col1.y; + var a31:Number = col1.z; + var a12:Number = col2.x; + var a22:Number = col2.y; + var a32:Number = col2.z; + var a13:Number = col3.x; + var a23:Number = col3.y; + var a33:Number = col3.z; + //float32 det = b2Dot(col1, b2Cross(col2, col3)); + var det:Number = a11 * (a22 * a33 - a32 * a23) + + a21 * (a32 * a13 - a12 * a33) + + a31 * (a12 * a23 - a22 * a13); + if (det != 0.0) + { + det = 1.0 / det; + } + //out.x = det * b2Dot(b, b2Cross(col2, col3)); + out.x = det * ( bX * (a22 * a33 - a32 * a23) + + bY * (a32 * a13 - a12 * a33) + + bZ * (a12 * a23 - a22 * a13) ); + //out.y = det * b2Dot(col1, b2Cross(b, col3)); + out.y = det * ( a11 * (bY * a33 - bZ * a23) + + a21 * (bZ * a13 - bX * a33) + + a31 * (bX * a23 - bY * a13)); + //out.z = det * b2Dot(col1, b2Cross(col2, b)); + out.z = det * ( a11 * (a22 * bZ - a32 * bY) + + a21 * (a32 * bX - a12 * bZ) + + a31 * (a12 * bY - a22 * bX)); + return out; + } + + public var col1:b2Vec3 = new b2Vec3(); + public var col2:b2Vec3 = new b2Vec3(); + public var col3:b2Vec3 = new b2Vec3(); +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Common/Math/b2Math.as b/srclib/Box2D/Common/Math/b2Math.as new file mode 100644 index 00000000..c2569275 --- /dev/null +++ b/srclib/Box2D/Common/Math/b2Math.as @@ -0,0 +1,268 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Common.Math{ + + +/** +* @private +*/ +public class b2Math{ + + /** + * This function is used to ensure that a floating point number is + * not a NaN or infinity. + */ + static public function IsValid(x:Number) : Boolean + { + return isFinite(x); + } + + /*static public function b2InvSqrt(x:Number):Number{ + union + { + float32 x; + int32 i; + } convert; + + convert.x = x; + float32 xhalf = 0.5f * x; + convert.i = 0x5f3759df - (convert.i >> 1); + x = convert.x; + x = x * (1.5f - xhalf * x * x); + return x; + }*/ + + static public function Dot(a:b2Vec2, b:b2Vec2):Number + { + return a.x * b.x + a.y * b.y; + } + + static public function CrossVV(a:b2Vec2, b:b2Vec2):Number + { + return a.x * b.y - a.y * b.x; + } + + static public function CrossVF(a:b2Vec2, s:Number):b2Vec2 + { + var v:b2Vec2 = new b2Vec2(s * a.y, -s * a.x); + return v; + } + + static public function CrossFV(s:Number, a:b2Vec2):b2Vec2 + { + var v:b2Vec2 = new b2Vec2(-s * a.y, s * a.x); + return v; + } + + static public function MulMV(A:b2Mat22, v:b2Vec2):b2Vec2 + { + // (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y) + // (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y) + var u:b2Vec2 = new b2Vec2(A.col1.x * v.x + A.col2.x * v.y, A.col1.y * v.x + A.col2.y * v.y); + return u; + } + + static public function MulTMV(A:b2Mat22, v:b2Vec2):b2Vec2 + { + // (tVec.x * tMat.col1.x + tVec.y * tMat.col1.y) + // (tVec.x * tMat.col2.x + tVec.y * tMat.col2.y) + var u:b2Vec2 = new b2Vec2(Dot(v, A.col1), Dot(v, A.col2)); + return u; + } + + static public function MulX(T:b2Transform, v:b2Vec2) : b2Vec2 + { + var a:b2Vec2 = MulMV(T.R, v); + a.x += T.position.x; + a.y += T.position.y; + //return T.position + b2Mul(T.R, v); + return a; + } + + static public function MulXT(T:b2Transform, v:b2Vec2):b2Vec2 + { + var a:b2Vec2 = SubtractVV(v, T.position); + //return b2MulT(T.R, v - T.position); + var tX:Number = (a.x * T.R.col1.x + a.y * T.R.col1.y ); + a.y = (a.x * T.R.col2.x + a.y * T.R.col2.y ); + a.x = tX; + return a; + } + + static public function AddVV(a:b2Vec2, b:b2Vec2):b2Vec2 + { + var v:b2Vec2 = new b2Vec2(a.x + b.x, a.y + b.y); + return v; + } + + static public function SubtractVV(a:b2Vec2, b:b2Vec2):b2Vec2 + { + var v:b2Vec2 = new b2Vec2(a.x - b.x, a.y - b.y); + return v; + } + + static public function Distance(a:b2Vec2, b:b2Vec2) : Number{ + var cX:Number = a.x-b.x; + var cY:Number = a.y-b.y; + return Math.sqrt(cX*cX + cY*cY); + } + + static public function DistanceSquared(a:b2Vec2, b:b2Vec2) : Number{ + var cX:Number = a.x-b.x; + var cY:Number = a.y-b.y; + return (cX*cX + cY*cY); + } + + static public function MulFV(s:Number, a:b2Vec2):b2Vec2 + { + var v:b2Vec2 = new b2Vec2(s * a.x, s * a.y); + return v; + } + + static public function AddMM(A:b2Mat22, B:b2Mat22):b2Mat22 + { + var C:b2Mat22 = b2Mat22.FromVV(AddVV(A.col1, B.col1), AddVV(A.col2, B.col2)); + return C; + } + + // A * B + static public function MulMM(A:b2Mat22, B:b2Mat22):b2Mat22 + { + var C:b2Mat22 = b2Mat22.FromVV(MulMV(A, B.col1), MulMV(A, B.col2)); + return C; + } + + // A^T * B + static public function MulTMM(A:b2Mat22, B:b2Mat22):b2Mat22 + { + var c1:b2Vec2 = new b2Vec2(Dot(A.col1, B.col1), Dot(A.col2, B.col1)); + var c2:b2Vec2 = new b2Vec2(Dot(A.col1, B.col2), Dot(A.col2, B.col2)); + var C:b2Mat22 = b2Mat22.FromVV(c1, c2); + return C; + } + + static public function Abs(a:Number):Number + { + return a > 0.0 ? a : -a; + } + + static public function AbsV(a:b2Vec2):b2Vec2 + { + var b:b2Vec2 = new b2Vec2(Abs(a.x), Abs(a.y)); + return b; + } + + static public function AbsM(A:b2Mat22):b2Mat22 + { + var B:b2Mat22 = b2Mat22.FromVV(AbsV(A.col1), AbsV(A.col2)); + return B; + } + + static public function Min(a:Number, b:Number):Number + { + return a < b ? a : b; + } + + static public function MinV(a:b2Vec2, b:b2Vec2):b2Vec2 + { + var c:b2Vec2 = new b2Vec2(Min(a.x, b.x), Min(a.y, b.y)); + return c; + } + + static public function Max(a:Number, b:Number):Number + { + return a > b ? a : b; + } + + static public function MaxV(a:b2Vec2, b:b2Vec2):b2Vec2 + { + var c:b2Vec2 = new b2Vec2(Max(a.x, b.x), Max(a.y, b.y)); + return c; + } + + static public function Clamp(a:Number, low:Number, high:Number):Number + { + return a < low ? low : a > high ? high : a; + } + + static public function ClampV(a:b2Vec2, low:b2Vec2, high:b2Vec2):b2Vec2 + { + return MaxV(low, MinV(a, high)); + } + + static public function Swap(a:Array, b:Array) : void + { + var tmp:* = a[0]; + a[0] = b[0]; + b[0] = tmp; + } + + // b2Random number in range [-1,1] + static public function Random():Number + { + return Math.random() * 2 - 1; + } + + static public function RandomRange(lo:Number, hi:Number) : Number + { + var r:Number = Math.random(); + r = (hi - lo) * r + lo; + return r; + } + + // "Next Largest Power of 2 + // Given a binary integer value x, the next largest power of 2 can be computed by a SWAR algorithm + // that recursively "folds" the upper bits into the lower bits. This process yields a bit vector with + // the same most significant 1 as x, but all 1's below it. Adding 1 to that value yields the next + // largest power of 2. For a 32-bit value:" + static public function NextPowerOfTwo(x:uint):uint + { + x |= (x >> 1) & 0x7FFFFFFF; + x |= (x >> 2) & 0x3FFFFFFF; + x |= (x >> 4) & 0x0FFFFFFF; + x |= (x >> 8) & 0x00FFFFFF; + x |= (x >> 16)& 0x0000FFFF; + return x + 1; + } + + static public function IsPowerOfTwo(x:uint):Boolean + { + var result:Boolean = x > 0 && (x & (x - 1)) == 0; + return result; + } + + + // Temp vector functions to reduce calls to 'new' + /*static public var tempVec:b2Vec2 = new b2Vec2(); + static public var tempVec2:b2Vec2 = new b2Vec2(); + static public var tempVec3:b2Vec2 = new b2Vec2(); + static public var tempVec4:b2Vec2 = new b2Vec2(); + static public var tempVec5:b2Vec2 = new b2Vec2(); + + static public var tempMat:b2Mat22 = new b2Mat22(); + + static public var tempAABB:b2AABB = new b2AABB(); */ + + static public const b2Vec2_zero:b2Vec2 = new b2Vec2(0.0, 0.0); + static public const b2Mat22_identity:b2Mat22 = b2Mat22.FromVV(new b2Vec2(1.0, 0.0), new b2Vec2(0.0, 1.0)); + static public const b2Transform_identity:b2Transform = new b2Transform(b2Vec2_zero, b2Mat22_identity); + + +} +} diff --git a/srclib/Box2D/Common/Math/b2Sweep.as b/srclib/Box2D/Common/Math/b2Sweep.as new file mode 100644 index 00000000..7123bfac --- /dev/null +++ b/srclib/Box2D/Common/Math/b2Sweep.as @@ -0,0 +1,104 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Common.Math{ + + + + +/** +* This describes the motion of a body/shape for TOI computation. +* Shapes are defined with respect to the body origin, which may +* no coincide with the center of mass. However, to support dynamics +* we must interpolate the center of mass position. +*/ +public class b2Sweep +{ + public function b2Sweep() {} + + public function Set(other:b2Sweep):void + { + localCenter.SetV(other.localCenter); + c0.SetV(other.c0); + c.SetV(other.c); + a0 = other.a0; + a = other.a; + t0 = other.t0; + } + + public function Copy():b2Sweep + { + var copy:b2Sweep = new b2Sweep(); + copy.localCenter.SetV(localCenter); + copy.c0.SetV(c0); + copy.c.SetV(c); + copy.a0 = a0; + copy.a = a; + copy.t0 = t0; + return copy; + } + + /** + * Get the interpolated transform at a specific time. + * @param alpha is a factor in [0,1], where 0 indicates t0. + */ + public function GetTransform(xf:b2Transform, alpha:Number):void + { + xf.position.x = (1.0 - alpha) * c0.x + alpha * c.x; + xf.position.y = (1.0 - alpha) * c0.y + alpha * c.y; + var angle:Number = (1.0 - alpha) * a0 + alpha * a; + xf.R.Set(angle); + + // Shift to origin + //xf->position -= b2Mul(xf->R, localCenter); + var tMat:b2Mat22 = xf.R; + xf.position.x -= (tMat.col1.x * localCenter.x + tMat.col2.x * localCenter.y); + xf.position.y -= (tMat.col1.y * localCenter.x + tMat.col2.y * localCenter.y); + } + + /** + * Advance the sweep forward, yielding a new initial state. + * @param t the new initial time. + */ + public function Advance(t:Number) : void{ + if (t0 < t && 1.0 - t0 > Number.MIN_VALUE) + { + var alpha:Number = (t - t0) / (1.0 - t0); + //c0 = (1.0f - alpha) * c0 + alpha * c; + c0.x = (1.0 - alpha) * c0.x + alpha * c.x; + c0.y = (1.0 - alpha) * c0.y + alpha * c.y; + a0 = (1.0 - alpha) * a0 + alpha * a; + t0 = t; + } + } + + /** Local center of mass position */ + public var localCenter:b2Vec2 = new b2Vec2(); + /** Center world position */ + public var c0:b2Vec2 = new b2Vec2; + /** Center world position */ + public var c:b2Vec2 = new b2Vec2(); + /** World angle */ + public var a0:Number; + /** World angle */ + public var a:Number; + /** Time interval = [t0,1], where t0 is in [0,1] */ + public var t0:Number; +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Common/Math/b2Transform.as b/srclib/Box2D/Common/Math/b2Transform.as new file mode 100644 index 00000000..c5051764 --- /dev/null +++ b/srclib/Box2D/Common/Math/b2Transform.as @@ -0,0 +1,81 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Common.Math{ + + + + +/** +* A transform contains translation and rotation. It is used to represent +* the position and orientation of rigid frames. +*/ +public class b2Transform +{ + /** + * The default constructor does nothing (for performance). + */ + public function b2Transform(pos:b2Vec2=null, r:b2Mat22=null) : void + { + if (pos){ + position.SetV(pos); + R.SetM(r); + + } + } + + /** + * Initialize using a position vector and a rotation matrix. + */ + public function Initialize(pos:b2Vec2, r:b2Mat22) : void + { + position.SetV(pos); + R.SetM(r); + } + + /** + * Set this to the identity transform. + */ + public function SetIdentity() : void + { + position.SetZero(); + R.SetIdentity(); + } + + public function Set(x:b2Transform) : void{ + + position.SetV(x.position); + + R.SetM(x.R); + + } + + /** + * Calculate the angle that the rotation matrix represents. + */ + public function GetAngle():Number + { + return Math.atan2(R.col1.y, R.col1.x); + } + + + public var position:b2Vec2 = new b2Vec2; + public var R:b2Mat22 = new b2Mat22(); +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Common/Math/b2Vec2.as b/srclib/Box2D/Common/Math/b2Vec2.as new file mode 100644 index 00000000..2a0eaccd --- /dev/null +++ b/srclib/Box2D/Common/Math/b2Vec2.as @@ -0,0 +1,142 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Common.Math{ + + + + +/** +* A 2D column vector. +*/ + +public class b2Vec2 +{ + public function b2Vec2(x_:Number=0, y_:Number=0) : void {x=x_; y=y_;}; + + public function SetZero() : void { x = 0.0; y = 0.0; } + public function Set(x_:Number=0, y_:Number=0) : void {x=x_; y=y_;}; + public function SetV(v:b2Vec2) : void {x=v.x; y=v.y;}; + + public function GetNegative():b2Vec2 { return new b2Vec2(-x, -y); } + public function NegativeSelf():void { x = -x; y = -y; } + + static public function Make(x_:Number, y_:Number):b2Vec2 + { + return new b2Vec2(x_, y_); + } + + public function Copy():b2Vec2{ + return new b2Vec2(x,y); + } + + public function Add(v:b2Vec2) : void + { + x += v.x; y += v.y; + } + + public function Subtract(v:b2Vec2) : void + { + x -= v.x; y -= v.y; + } + + public function Multiply(a:Number) : void + { + x *= a; y *= a; + } + + public function MulM(A:b2Mat22) : void + { + var tX:Number = x; + x = A.col1.x * tX + A.col2.x * y; + y = A.col1.y * tX + A.col2.y * y; + } + + public function MulTM(A:b2Mat22) : void + { + var tX:Number = b2Math.Dot(this, A.col1); + y = b2Math.Dot(this, A.col2); + x = tX; + } + + public function CrossVF(s:Number) : void + { + var tX:Number = x; + x = s * y; + y = -s * tX; + } + + public function CrossFV(s:Number) : void + { + var tX:Number = x; + x = -s * y; + y = s * tX; + } + + public function MinV(b:b2Vec2) : void + { + x = x < b.x ? x : b.x; + y = y < b.y ? y : b.y; + } + + public function MaxV(b:b2Vec2) : void + { + x = x > b.x ? x : b.x; + y = y > b.y ? y : b.y; + } + + public function Abs() : void + { + if (x < 0) x = -x; + if (y < 0) y = -y; + } + + public function Length():Number + { + return Math.sqrt(x * x + y * y); + } + + public function LengthSquared():Number + { + return (x * x + y * y); + } + + public function Normalize():Number + { + var length:Number = Math.sqrt(x * x + y * y); + if (length < Number.MIN_VALUE) + { + return 0.0; + } + var invLength:Number = 1.0 / length; + x *= invLength; + y *= invLength; + + return length; + } + + public function IsValid():Boolean + { + return b2Math.IsValid(x) && b2Math.IsValid(y); + } + + public var x:Number; + public var y:Number; +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Common/Math/b2Vec3.as b/srclib/Box2D/Common/Math/b2Vec3.as new file mode 100644 index 00000000..d6c830b2 --- /dev/null +++ b/srclib/Box2D/Common/Math/b2Vec3.as @@ -0,0 +1,96 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Common.Math{ + + + + +/** +* A 2D column vector with 3 elements. +*/ + +public class b2Vec3 +{ + /** + * Construct using co-ordinates + */ + public function b2Vec3(x:Number = 0, y:Number = 0, z:Number = 0) + { + this.x = x; + this.y = y; + this.z = z; + } + + /** + * Sets this vector to all zeros + */ + public function SetZero():void + { + x = y = z = 0.0; + } + + /** + * Set this vector to some specified coordinates. + */ + public function Set(x:Number, y:Number, z:Number):void + { + this.x = x; + this.y = y; + this.z = z; + } + + public function SetV(v:b2Vec3):void + { + x = v.x; + y = v.y; + z = v.z; + } + + /** + * Negate this vector + */ + public function GetNegative():b2Vec3 { return new b2Vec3( -x, -y, -z); } + + public function NegativeSelf():void { x = -x; y = -y; z = -z; } + + public function Copy():b2Vec3{ + return new b2Vec3(x,y,z); + } + + public function Add(v:b2Vec3) : void + { + x += v.x; y += v.y; z += v.z; + } + + public function Subtract(v:b2Vec3) : void + { + x -= v.x; y -= v.y; z -= v.z; + } + + public function Multiply(a:Number) : void + { + x *= a; y *= a; z *= a; + } + + public var x:Number; + public var y:Number; + public var z:Number; + +} +} \ No newline at end of file diff --git a/srclib/Box2D/Common/b2Color.as b/srclib/Box2D/Common/b2Color.as new file mode 100644 index 00000000..82b55e7b --- /dev/null +++ b/srclib/Box2D/Common/b2Color.as @@ -0,0 +1,68 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Common{ + + import Box2D.Common.Math.*; + + + +/** +* Color for debug drawing. Each value has the range [0,1]. +*/ + +public class b2Color +{ + + public function b2Color(rr:Number, gg:Number, bb:Number){ + _r = uint(255 * b2Math.Clamp(rr, 0.0, 1.0)); + _g = uint(255 * b2Math.Clamp(gg, 0.0, 1.0)); + _b = uint(255 * b2Math.Clamp(bb, 0.0, 1.0)); + } + + public function Set(rr:Number, gg:Number, bb:Number):void{ + _r = uint(255 * b2Math.Clamp(rr, 0.0, 1.0)); + _g = uint(255 * b2Math.Clamp(gg, 0.0, 1.0)); + _b = uint(255 * b2Math.Clamp(bb, 0.0, 1.0)); + } + + // R + public function set r(rr:Number) : void{ + _r = uint(255 * b2Math.Clamp(rr, 0.0, 1.0)); + } + // G + public function set g(gg:Number) : void{ + _g = uint(255 * b2Math.Clamp(gg, 0.0, 1.0)); + } + // B + public function set b(bb:Number) : void{ + _b = uint(255 * b2Math.Clamp(bb, 0.0, 1.0)); + } + + // Color + public function get color() : uint{ + return (_r << 16) | (_g << 8) | (_b); + } + + private var _r:uint = 0; + private var _g:uint = 0; + private var _b:uint = 0; + +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Common/b2Settings.as b/srclib/Box2D/Common/b2Settings.as new file mode 100644 index 00000000..1df65f3b --- /dev/null +++ b/srclib/Box2D/Common/b2Settings.as @@ -0,0 +1,193 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Common{ + + + + +/** +* This class controls Box2D global settings +*/ +public class b2Settings{ + + /** + * The current version of Box2D + */ + static public const VERSION:String = "2.1alpha"; + + static public const USHRT_MAX:int = 0x0000ffff; + + static public const b2_pi:Number = Math.PI; + + + + // Collision + /** + * Number of manifold points in a b2Manifold. This should NEVER change. + */ + static public const b2_maxManifoldPoints:int = 2; + + /* + * The growable broadphase doesn't have upper limits, + * so there is no b2_maxProxies or b2_maxPairs settings. + */ + //static public const b2_maxProxies:int = 0; + //static public const b2_maxPairs:int = 8 * b2_maxProxies; + + /** + * This is used to fatten AABBs in the dynamic tree. This allows proxies + * to move by a small amount without triggering a tree adjustment. + * This is in meters. + */ + static public const b2_aabbExtension:Number = 0.1; + + /** + * This is used to fatten AABBs in the dynamic tree. This is used to predict + * the future position based on the current displacement. + * This is a dimensionless multiplier. + */ + static public const b2_aabbMultiplier:Number = 2.0; + + /** + * The radius of the polygon/edge shape skin. This should not be modified. Making + * this smaller means polygons will have and insufficient for continuous collision. + * Making it larger may create artifacts for vertex collision. + */ + static public const b2_polygonRadius:Number = 2.0 * b2_linearSlop; + + // Dynamics + + /** + * A small length used as a collision and constraint tolerance. Usually it is + * chosen to be numerically significant, but visually insignificant. + */ + static public const b2_linearSlop:Number = 0.005; // 0.5 cm + + /** + * A small angle used as a collision and constraint tolerance. Usually it is + * chosen to be numerically significant, but visually insignificant. + */ + static public const b2_angularSlop:Number = 2.0 / 180.0 * b2_pi; // 2 degrees + + /** + * Continuous collision detection (CCD) works with core, shrunken shapes. This is the + * amount by which shapes are automatically shrunk to work with CCD. This must be + * larger than b2_linearSlop. + * @see b2_linearSlop + */ + static public const b2_toiSlop:Number = 8.0 * b2_linearSlop; + + /** + * Maximum number of contacts to be handled to solve a TOI island. + */ + static public const b2_maxTOIContactsPerIsland:int = 32; + + /** + * Maximum number of joints to be handled to solve a TOI island. + */ + static public const b2_maxTOIJointsPerIsland:int = 32; + + /** + * A velocity threshold for elastic collisions. Any collision with a relative linear + * velocity below this threshold will be treated as inelastic. + */ + static public const b2_velocityThreshold:Number = 1.0; // 1 m/s + + /** + * The maximum linear position correction used when solving constraints. This helps to + * prevent overshoot. + */ + static public const b2_maxLinearCorrection:Number = 0.2; // 20 cm + + /** + * The maximum angular position correction used when solving constraints. This helps to + * prevent overshoot. + */ + static public const b2_maxAngularCorrection:Number = 8.0 / 180.0 * b2_pi; // 8 degrees + + /** + * The maximum linear velocity of a body. This limit is very large and is used + * to prevent numerical problems. You shouldn't need to adjust this. + */ + static public const b2_maxTranslation:Number = 2.0; + static public const b2_maxTranslationSquared:Number = b2_maxTranslation * b2_maxTranslation; + + /** + * The maximum angular velocity of a body. This limit is very large and is used + * to prevent numerical problems. You shouldn't need to adjust this. + */ + static public const b2_maxRotation:Number = 0.5 * b2_pi; + static public const b2_maxRotationSquared:Number = b2_maxRotation * b2_maxRotation; + + /** + * This scale factor controls how fast overlap is resolved. Ideally this would be 1 so + * that overlap is removed in one time step. However using values close to 1 often lead + * to overshoot. + */ + static public const b2_contactBaumgarte:Number = 0.2; + + /** + * Friction mixing law. Feel free to customize this. + */ + public static function b2MixFriction(friction1:Number, friction2:Number):Number + { + return Math.sqrt(friction1 * friction2); + } + + /** + * Restitution mixing law. Feel free to customize this. + */ + public static function b2MixRestitution(restitution1:Number, restitution2:Number):Number + { + return restitution1 > restitution2 ? restitution1 : restitution2; + } + + + + // Sleep + + /** + * The time that a body must be still before it will go to sleep. + */ + static public const b2_timeToSleep:Number = 0.5; // half a second + /** + * A body cannot sleep if its linear velocity is above this tolerance. + */ + static public const b2_linearSleepTolerance:Number = 0.01; // 1 cm/s + /** + * A body cannot sleep if its angular velocity is above this tolerance. + */ + static public const b2_angularSleepTolerance:Number = 2.0 / 180.0 * b2Settings.b2_pi; // 2 degrees/s + + // assert + /** + * b2Assert is used internally to handle assertions. By default, calls are commented out to save performance, + * so they serve more as documentation than anything else. + */ + static public function b2Assert(a:Boolean) : void + { + if (!a){ + //var nullVec:b2Vec2; + //nullVec.x++; + throw "Assertion Failed"; + } + } +} + +} diff --git a/srclib/Box2D/Common/b2internal.as b/srclib/Box2D/Common/b2internal.as new file mode 100644 index 00000000..aa119545 --- /dev/null +++ b/srclib/Box2D/Common/b2internal.as @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Common { + public namespace b2internal = "http://www.box2d.org/ns/b2internal"; +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2CircleContact.as b/srclib/Box2D/Dynamics/Contacts/b2CircleContact.as new file mode 100644 index 00000000..bb11577c --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2CircleContact.as @@ -0,0 +1,57 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + + import Box2D.Collision.*; + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Dynamics.*; +use namespace b2internal; + +/** +* @private +*/ +public class b2CircleContact extends b2Contact +{ + static public function Create(allocator:*):b2Contact{ + return new b2CircleContact(); + } + static public function Destroy(contact:b2Contact, allocator:*) : void{ + // + } + + public function Reset(fixtureA:b2Fixture, fixtureB:b2Fixture):void{ + super.Reset(fixtureA, fixtureB); + //b2Settings.b2Assert(m_shape1.m_type == b2Shape.e_circleShape); + //b2Settings.b2Assert(m_shape2.m_type == b2Shape.e_circleShape); + } + //~b2CircleContact() {} + + b2internal override function Evaluate() : void{ + var bA:b2Body = m_fixtureA.GetBody(); + var bB:b2Body = m_fixtureB.GetBody(); + + b2Collision.CollideCircles(m_manifold, + m_fixtureA.GetShape() as b2CircleShape, bA.m_xf, + m_fixtureB.GetShape() as b2CircleShape, bB.m_xf); + } +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2Contact.as b/srclib/Box2D/Dynamics/Contacts/b2Contact.as new file mode 100644 index 00000000..9a575920 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2Contact.as @@ -0,0 +1,372 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + + import Box2D.Collision.*; + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; +use namespace b2internal; + + +//typedef b2Contact* b2ContactCreateFcn(b2Shape* shape1, b2Shape* shape2, b2BlockAllocator* allocator); +//typedef void b2ContactDestroyFcn(b2Contact* contact, b2BlockAllocator* allocator); + + + +/** +* The class manages contact between two shapes. A contact exists for each overlapping +* AABB in the broad-phase (except if filtered). Therefore a contact object may exist +* that has no contact points. +*/ +public class b2Contact +{ + /** + * Get the contact manifold. Do not modify the manifold unless you understand the + * internals of Box2D + */ + public function GetManifold():b2Manifold + { + return m_manifold; + } + + /** + * Get the world manifold + */ + public function GetWorldManifold(worldManifold:b2WorldManifold):void + { + var bodyA:b2Body = m_fixtureA.GetBody(); + var bodyB:b2Body = m_fixtureB.GetBody(); + var shapeA:b2Shape = m_fixtureA.GetShape(); + var shapeB:b2Shape = m_fixtureB.GetShape(); + + worldManifold.Initialize(m_manifold, bodyA.GetTransform(), shapeA.m_radius, bodyB.GetTransform(), shapeB.m_radius); + } + + public var normal:b2Vec2; + public var contactPoints:Vector.; + + /** + * Is this contact touching. + */ + public function IsTouching():Boolean + { + return (m_flags & e_touchingFlag) == e_touchingFlag; + } + + /** + * Does this contact generate TOI events for continuous simulation + */ + public function IsContinuous():Boolean + { + return (m_flags & e_continuousFlag) == e_continuousFlag; + } + + /** + * Change this to be a sensor or-non-sensor contact. + */ + public function SetSensor(sensor:Boolean):void{ + if (sensor) + { + m_flags |= e_sensorFlag; + } + else + { + m_flags &= ~e_sensorFlag; + } + } + + /** + * Is this contact a sensor? + */ + public function IsSensor():Boolean{ + return (m_flags & e_sensorFlag) == e_sensorFlag; + } + + /** + * Enable/disable this contact. This can be used inside the pre-solve + * contact listener. The contact is only disabled for the current + * time step (or sub-step in continuous collision). + */ + public function SetEnabled(flag:Boolean):void{ + if (flag) + { + m_flags |= e_enabledFlag; + } + else + { + m_flags &= ~e_enabledFlag; + } + } + + /** + * Has this contact been disabled? + * @return + */ + public function IsEnabled():Boolean { + return (m_flags & e_enabledFlag) == e_enabledFlag; + } + + /** + * Get the next contact in the world's contact list. + */ + public function GetNext():b2Contact{ + return m_next; + } + + /** + * Get the first fixture in this contact. + */ + public function GetFixtureA():b2Fixture + { + return m_fixtureA; + } + + /** + * Get the second fixture in this contact. + */ + public function GetFixtureB():b2Fixture + { + return m_fixtureB; + } + + /** + * Flag this contact for filtering. Filtering will occur the next time step. + */ + public function FlagForFiltering():void + { + m_flags |= e_filterFlag; + } + + //--------------- Internals Below ------------------- + + // m_flags + // enum + // This contact should not participate in Solve + // The contact equivalent of sensors + static b2internal var e_sensorFlag:uint = 0x0001; + // Generate TOI events. + static b2internal var e_continuousFlag:uint = 0x0002; + // Used when crawling contact graph when forming islands. + static b2internal var e_islandFlag:uint = 0x0004; + // Used in SolveTOI to indicate the cached toi value is still valid. + static b2internal var e_toiFlag:uint = 0x0008; + // Set when shapes are touching + static b2internal var e_touchingFlag:uint = 0x0010; + // This contact can be disabled (by user) + static b2internal var e_enabledFlag:uint = 0x0020; + // This contact needs filtering because a fixture filter was changed. + static b2internal var e_filterFlag:uint = 0x0040; + + public function b2Contact() + { + // Real work is done in Reset + } + + /** @private */ + b2internal function Reset(fixtureA:b2Fixture = null, fixtureB:b2Fixture = null):void + { + m_flags = e_enabledFlag; + + if (!fixtureA || !fixtureB){ + m_fixtureA = null; + m_fixtureB = null; + return; + } + + if (fixtureA.IsSensor() || fixtureB.IsSensor()) + { + m_flags |= e_sensorFlag; + } + + var bodyA:b2Body = fixtureA.GetBody(); + var bodyB:b2Body = fixtureB.GetBody(); + + if (bodyA.GetType() != b2Body.b2_dynamicBody || bodyA.IsBullet() || bodyB.GetType() != b2Body.b2_dynamicBody || bodyB.IsBullet()) + { + m_flags |= e_continuousFlag; + } + + m_fixtureA = fixtureA; + m_fixtureB = fixtureB; + + m_manifold.m_pointCount = 0; + + m_prev = null; + m_next = null; + + m_nodeA.contact = null; + m_nodeA.prev = null; + m_nodeA.next = null; + m_nodeA.other = null; + + m_nodeB.contact = null; + m_nodeB.prev = null; + m_nodeB.next = null; + m_nodeB.other = null; + } + + b2internal function Update(listener:b2ContactListener) : void + { + // Swap old & new manifold + var tManifold:b2Manifold = m_oldManifold; + m_oldManifold = m_manifold; + m_manifold = tManifold; + + // Re-enable this contact + m_flags |= e_enabledFlag; + + var touching:Boolean = false; + var wasTouching:Boolean = (m_flags & e_touchingFlag) == e_touchingFlag; + + var bodyA:b2Body = m_fixtureA.m_body; + var bodyB:b2Body = m_fixtureB.m_body; + + var aabbOverlap:Boolean = m_fixtureA.m_aabb.TestOverlap(m_fixtureB.m_aabb); + + // Is this contat a sensor? + if (m_flags & e_sensorFlag) + { + if (aabbOverlap) + { + var shapeA:b2Shape = m_fixtureA.GetShape(); + var shapeB:b2Shape = m_fixtureB.GetShape(); + var xfA:b2Transform = bodyA.GetTransform(); + var xfB:b2Transform = bodyB.GetTransform(); + touching = b2Shape.TestOverlap(shapeA, xfA, shapeB, xfB); + } + + // Sensors don't generate manifolds + m_manifold.m_pointCount = 0; + } + else + { + // Slow contacts don't generate TOI events. + if (bodyA.GetType() != b2Body.b2_dynamicBody || bodyA.IsBullet() || bodyB.GetType() != b2Body.b2_dynamicBody || bodyB.IsBullet()) + { + m_flags |= e_continuousFlag; + } + else + { + m_flags &= ~e_continuousFlag; + } + + if (aabbOverlap) + { + Evaluate(); + + touching = m_manifold.m_pointCount > 0; + + // Match old contact ids to new contact ids and copy the + // stored impulses to warm start the solver. + for (var i:int = 0; i < m_manifold.m_pointCount; ++i) + { + var mp2:b2ManifoldPoint = m_manifold.m_points[i]; + mp2.m_normalImpulse = 0.0; + mp2.m_tangentImpulse = 0.0; + var id2:b2ContactID = mp2.m_id; + + for (var j:int = 0; j < m_oldManifold.m_pointCount; ++j) + { + var mp1:b2ManifoldPoint = m_oldManifold.m_points[j]; + + if (mp1.m_id.key == id2.key) + { + mp2.m_normalImpulse = mp1.m_normalImpulse; + mp2.m_tangentImpulse = mp1.m_tangentImpulse; + break; + } + } + } + + } + else + { + m_manifold.m_pointCount = 0; + } + if (touching != wasTouching) + { + bodyA.SetAwake(true); + bodyB.SetAwake(true); + } + } + + if (touching) + { + m_flags |= e_touchingFlag; + } + else + { + m_flags &= ~e_touchingFlag; + } + + if (wasTouching == false && touching == true) + { + listener.BeginContact(this); + } + + if (wasTouching == true && touching == false) + { + listener.EndContact(this); + } + + if ((m_flags & e_sensorFlag) == 0) + { + listener.PreSolve(this, m_oldManifold); + } + } + + //virtual ~b2Contact() {} + + b2internal virtual function Evaluate() : void{}; + + private static var s_input:b2TOIInput = new b2TOIInput(); + b2internal function ComputeTOI(sweepA:b2Sweep, sweepB:b2Sweep):Number + { + s_input.proxyA.Set(m_fixtureA.GetShape()); + s_input.proxyB.Set(m_fixtureB.GetShape()); + s_input.sweepA = sweepA; + s_input.sweepB = sweepB; + s_input.tolerance = b2Settings.b2_linearSlop; + return b2TimeOfImpact.TimeOfImpact(s_input); + } + + b2internal var m_flags:uint; + + // World pool and list pointers. + b2internal var m_prev:b2Contact; + b2internal var m_next:b2Contact; + + // Nodes for connecting bodies. + b2internal var m_nodeA:b2ContactEdge = new b2ContactEdge(); + b2internal var m_nodeB:b2ContactEdge = new b2ContactEdge(); + + b2internal var m_fixtureA:b2Fixture; + b2internal var m_fixtureB:b2Fixture; + + b2internal var m_manifold:b2Manifold = new b2Manifold(); + b2internal var m_oldManifold:b2Manifold = new b2Manifold(); + + b2internal var m_toi:Number; +}; + + +} diff --git a/srclib/Box2D/Dynamics/Contacts/b2ContactConstraint.as b/srclib/Box2D/Dynamics/Contacts/b2ContactConstraint.as new file mode 100644 index 00000000..177742ea --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2ContactConstraint.as @@ -0,0 +1,59 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + + import Box2D.Collision.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; +use namespace b2internal; + + +/** +* @private +*/ +public class b2ContactConstraint +{ + public function b2ContactConstraint(){ + points = new Vector.(b2Settings.b2_maxManifoldPoints); + for (var i:int = 0; i < b2Settings.b2_maxManifoldPoints; i++){ + points[i] = new b2ContactConstraintPoint(); + } + + + } + public var points:Vector.; + public var localPlaneNormal:b2Vec2 = new b2Vec2(); + public var localPoint:b2Vec2 = new b2Vec2(); + public var normal:b2Vec2 = new b2Vec2(); + public var normalMass:b2Mat22 = new b2Mat22(); + public var K:b2Mat22 = new b2Mat22(); + public var bodyA:b2Body; + public var bodyB:b2Body; + public var type:int;//b2Manifold::Type + public var radius:Number; + public var friction:Number; + public var restitution:Number; + public var pointCount:int; + public var manifold:b2Manifold; +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2ContactConstraintPoint.as b/srclib/Box2D/Dynamics/Contacts/b2ContactConstraintPoint.as new file mode 100644 index 00000000..a45eab76 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2ContactConstraintPoint.as @@ -0,0 +1,46 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; +use namespace b2internal; + + +/** +* @private +*/ +public class b2ContactConstraintPoint +{ + public function b2ContactConstraintPoint() {} + + public var localPoint:b2Vec2=new b2Vec2(); + public var rA:b2Vec2=new b2Vec2(); + public var rB:b2Vec2=new b2Vec2(); + public var normalImpulse:Number; + public var tangentImpulse:Number; + public var normalMass:Number; + public var tangentMass:Number; + public var equalizedMass:Number; + public var velocityBias:Number; +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2ContactEdge.as b/srclib/Box2D/Dynamics/Contacts/b2ContactEdge.as new file mode 100644 index 00000000..d910fc02 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2ContactEdge.as @@ -0,0 +1,46 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts { + + import Box2D.Common.b2internal; + import Box2D.Dynamics.b2Body; + + +use namespace b2internal; + +/** +* A contact edge is used to connect bodies and contacts together +* in a contact graph where each body is a node and each contact +* is an edge. A contact edge belongs to a doubly linked list +* maintained in each attached body. Each contact has two contact +* nodes, one for each attached body. +*/ +public class b2ContactEdge +{ + public function b2ContactEdge() {} + + public var other:b2Body; + public var contact:b2Contact; + public var prev:b2ContactEdge; + public var next:b2ContactEdge; +}; + + + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2ContactFactory.as b/srclib/Box2D/Dynamics/Contacts/b2ContactFactory.as new file mode 100644 index 00000000..9b1439c6 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2ContactFactory.as @@ -0,0 +1,151 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Dynamics.*; +use namespace b2internal; + + +//typedef b2Contact* b2ContactCreateFcn(b2Shape* shape1, b2Shape* shape2, b2BlockAllocator* allocator); +//typedef void b2ContactDestroyFcn(b2Contact* contact, b2BlockAllocator* allocator); + + + +/** + * This class manages creation and destruction of b2Contact objects. + * @private + */ +public class b2ContactFactory +{ + function b2ContactFactory(allocator:*) + { + m_allocator = allocator; + InitializeRegisters(); + } + + b2internal function AddType(createFcn:Function, destroyFcn:Function, type1:int, type2:int) : void + { + //b2Settings.b2Assert(b2Shape.e_unknownShape < type1 && type1 < b2Shape.e_shapeTypeCount); + //b2Settings.b2Assert(b2Shape.e_unknownShape < type2 && type2 < b2Shape.e_shapeTypeCount); + + m_registers[type1][type2].createFcn = createFcn; + m_registers[type1][type2].destroyFcn = destroyFcn; + m_registers[type1][type2].primary = true; + + if (type1 != type2) + { + m_registers[type2][type1].createFcn = createFcn; + m_registers[type2][type1].destroyFcn = destroyFcn; + m_registers[type2][type1].primary = false; + } + } + b2internal function InitializeRegisters() : void{ + m_registers = new Vector. >(b2Shape.e_shapeTypeCount); + for (var i:int = 0; i < b2Shape.e_shapeTypeCount; i++){ + m_registers[i] = new Vector.(b2Shape.e_shapeTypeCount); + for (var j:int = 0; j < b2Shape.e_shapeTypeCount; j++){ + m_registers[i][j] = new b2ContactRegister(); + } + } + + AddType(b2CircleContact.Create, b2CircleContact.Destroy, b2Shape.e_circleShape, b2Shape.e_circleShape); + AddType(b2PolyAndCircleContact.Create, b2PolyAndCircleContact.Destroy, b2Shape.e_polygonShape, b2Shape.e_circleShape); + AddType(b2PolygonContact.Create, b2PolygonContact.Destroy, b2Shape.e_polygonShape, b2Shape.e_polygonShape); + + AddType(b2EdgeAndCircleContact.Create, b2EdgeAndCircleContact.Destroy, b2Shape.e_edgeShape, b2Shape.e_circleShape); + AddType(b2PolyAndEdgeContact.Create, b2PolyAndEdgeContact.Destroy, b2Shape.e_polygonShape, b2Shape.e_edgeShape); + } + public function Create(fixtureA:b2Fixture, fixtureB:b2Fixture):b2Contact{ + var type1:int = fixtureA.GetType(); + var type2:int = fixtureB.GetType(); + + //b2Settings.b2Assert(b2Shape.e_unknownShape < type1 && type1 < b2Shape.e_shapeTypeCount); + //b2Settings.b2Assert(b2Shape.e_unknownShape < type2 && type2 < b2Shape.e_shapeTypeCount); + + var reg:b2ContactRegister = m_registers[type1][type2]; + + var c:b2Contact; + + if (reg.pool) + { + // Pop a contact off the pool + c = reg.pool; + reg.pool = c.m_next; + reg.poolCount--; + c.Reset(fixtureA, fixtureB); + return c; + } + + var createFcn:Function = reg.createFcn; + if (createFcn != null) + { + if (reg.primary) + { + c = createFcn(m_allocator); + c.Reset(fixtureA, fixtureB); + return c; + } + else + { + c = createFcn(m_allocator); + c.Reset(fixtureB, fixtureA); + return c; + } + } + else + { + return null; + } + } + public function Destroy(contact:b2Contact) : void{ + if (contact.m_manifold.m_pointCount > 0) + { + contact.m_fixtureA.m_body.SetAwake(true); + contact.m_fixtureB.m_body.SetAwake(true); + } + + var type1:int = contact.m_fixtureA.GetType(); + var type2:int = contact.m_fixtureB.GetType(); + + //b2Settings.b2Assert(b2Shape.e_unknownShape < type1 && type1 < b2Shape.e_shapeTypeCount); + //b2Settings.b2Assert(b2Shape.e_unknownShape < type2 && type2 < b2Shape.e_shapeTypeCount); + + var reg:b2ContactRegister = m_registers[type1][type2]; + + if (true) + { + reg.poolCount++; + contact.m_next = reg.pool; + reg.pool = contact; + } + + var destroyFcn:Function = reg.destroyFcn; + destroyFcn(contact, m_allocator); + } + + + private var m_registers:Vector. >; + private var m_allocator:*; +}; + + +} diff --git a/srclib/Box2D/Dynamics/Contacts/b2ContactRegister.as b/srclib/Box2D/Dynamics/Contacts/b2ContactRegister.as new file mode 100644 index 00000000..912c6841 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2ContactRegister.as @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + +/** +* @private +*/ +public class b2ContactRegister +{ + public function b2ContactRegister() {} + + public var createFcn:Function; // fcn pointer + public var destroyFcn:Function;// fcn pointer + public var primary:Boolean; + public var pool:b2Contact; + public var poolCount:int; +}; + + + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2ContactResult.as b/srclib/Box2D/Dynamics/Contacts/b2ContactResult.as new file mode 100644 index 00000000..b97850a9 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2ContactResult.as @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + import Box2D.Collision.*; + import Box2D.Collision.Shapes.*; + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; +use namespace b2internal; + +/** +* This structure is used to report contact point results. +*/ +public class b2ContactResult +{ + public function b2ContactResult() {} + + /** The first shape */ + public var shape1:b2Shape; + /** The second shape */ + public var shape2:b2Shape; + /** Position in world coordinates */ + public var position:b2Vec2 = new b2Vec2(); + /** Points from shape1 to shape2 */ + public var normal:b2Vec2 = new b2Vec2(); + /** The normal impulse applied to body2 */ + public var normalImpulse:Number; + /** The tangent impulse applied to body2 */ + public var tangentImpulse:Number; + /** The contact id identifies the features in contact */ + public var id:b2ContactID = new b2ContactID(); +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2ContactSolver.as b/srclib/Box2D/Dynamics/Contacts/b2ContactSolver.as new file mode 100644 index 00000000..108b7819 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2ContactSolver.as @@ -0,0 +1,926 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + import Box2D.Collision.*; + import Box2D.Collision.Shapes.b2Shape; + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; + +use namespace b2internal; + + +/** +* @private +*/ +public class b2ContactSolver +{ + public function b2ContactSolver() + { + } + + private static var s_worldManifold:b2WorldManifold = new b2WorldManifold(); + public function Initialize(step:b2TimeStep, contacts:Vector., contactCount:int, allocator:*):void + { + var contact:b2Contact; + + m_step.Set(step) + + m_allocator = allocator; + + var i:int; + var tVec:b2Vec2; + var tMat:b2Mat22; + + m_constraintCount = contactCount; + + // fill vector to hold enough constraints + while (m_constraints.length < m_constraintCount) + { + m_constraints[m_constraints.length] = new b2ContactConstraint(); + } + + for (i = 0; i < contactCount; ++i) + { + contact = contacts[i]; + var fixtureA:b2Fixture = contact.m_fixtureA; + var fixtureB:b2Fixture = contact.m_fixtureB; + var shapeA:b2Shape = fixtureA.m_shape; + var shapeB:b2Shape = fixtureB.m_shape; + var radiusA:Number = shapeA.m_radius; + var radiusB:Number = shapeB.m_radius; + var bodyA:b2Body = fixtureA.m_body; + var bodyB:b2Body = fixtureB.m_body; + var manifold:b2Manifold = contact.GetManifold(); + + var friction:Number = b2Settings.b2MixFriction(fixtureA.GetFriction(), fixtureB.GetFriction()); + var restitution:Number = b2Settings.b2MixRestitution(fixtureA.GetRestitution(), fixtureB.GetRestitution()); + + //var vA:b2Vec2 = bodyA.m_linearVelocity.Copy(); + var vAX:Number = bodyA.m_linearVelocity.x; + var vAY:Number = bodyA.m_linearVelocity.y; + //var vB:b2Vec2 = bodyB.m_linearVelocity.Copy(); + var vBX:Number = bodyB.m_linearVelocity.x; + var vBY:Number = bodyB.m_linearVelocity.y; + var wA:Number = bodyA.m_angularVelocity; + var wB:Number = bodyB.m_angularVelocity; + + b2Settings.b2Assert(manifold.m_pointCount > 0); + + s_worldManifold.Initialize(manifold, bodyA.m_xf, radiusA, bodyB.m_xf, radiusB); + + var normalX:Number = s_worldManifold.m_normal.x; + var normalY:Number = s_worldManifold.m_normal.y; + + var cc:b2ContactConstraint = m_constraints[ i ]; + cc.bodyA = bodyA; //p + cc.bodyB = bodyB; //p + cc.manifold = manifold; //p + //c.normal = normal; + cc.normal.x = normalX; + cc.normal.y = normalY; + cc.pointCount = manifold.m_pointCount; + cc.friction = friction; + cc.restitution = restitution; + + cc.localPlaneNormal.x = manifold.m_localPlaneNormal.x; + cc.localPlaneNormal.y = manifold.m_localPlaneNormal.y; + cc.localPoint.x = manifold.m_localPoint.x; + cc.localPoint.y = manifold.m_localPoint.y; + cc.radius = radiusA + radiusB; + cc.type = manifold.m_type; + + for (var k:uint = 0; k < cc.pointCount; ++k) + { + var cp:b2ManifoldPoint = manifold.m_points[ k ]; + var ccp:b2ContactConstraintPoint = cc.points[ k ]; + + ccp.normalImpulse = cp.m_normalImpulse; + ccp.tangentImpulse = cp.m_tangentImpulse; + + ccp.localPoint.SetV(cp.m_localPoint); + + var rAX:Number = ccp.rA.x = s_worldManifold.m_points[k].x - bodyA.m_sweep.c.x; + var rAY:Number = ccp.rA.y = s_worldManifold.m_points[k].y - bodyA.m_sweep.c.y; + var rBX:Number = ccp.rB.x = s_worldManifold.m_points[k].x - bodyB.m_sweep.c.x; + var rBY:Number = ccp.rB.y = s_worldManifold.m_points[k].y - bodyB.m_sweep.c.y; + + var rnA:Number = rAX * normalY - rAY * normalX;//b2Math.b2Cross(r1, normal); + var rnB:Number = rBX * normalY - rBY * normalX;//b2Math.b2Cross(r2, normal); + + rnA *= rnA; + rnB *= rnB; + + var kNormal:Number = bodyA.m_invMass + bodyB.m_invMass + bodyA.m_invI * rnA + bodyB.m_invI * rnB; + //b2Settings.b2Assert(kNormal > Number.MIN_VALUE); + ccp.normalMass = 1.0 / kNormal; + + var kEqualized:Number = bodyA.m_mass * bodyA.m_invMass + bodyB.m_mass * bodyB.m_invMass; + kEqualized += bodyA.m_mass * bodyA.m_invI * rnA + bodyB.m_mass * bodyB.m_invI * rnB; + //b2Assert(kEqualized > Number.MIN_VALUE); + ccp.equalizedMass = 1.0 / kEqualized; + + //var tangent:b2Vec2 = b2Math.b2CrossVF(normal, 1.0); + var tangentX:Number = normalY + var tangentY:Number = -normalX; + + //var rtA:Number = b2Math.b2Cross(rA, tangent); + var rtA:Number = rAX*tangentY - rAY*tangentX; + //var rtB:Number = b2Math.b2Cross(rB, tangent); + var rtB:Number = rBX*tangentY - rBY*tangentX; + + rtA *= rtA; + rtB *= rtB; + + var kTangent:Number = bodyA.m_invMass + bodyB.m_invMass + bodyA.m_invI * rtA + bodyB.m_invI * rtB; + //b2Settings.b2Assert(kTangent > Number.MIN_VALUE); + ccp.tangentMass = 1.0 / kTangent; + + // Setup a velocity bias for restitution. + ccp.velocityBias = 0.0; + //b2Dot(c.normal, vB + b2Cross(wB, rB) - vA - b2Cross(wA, rA)); + var tX:Number = vBX + (-wB*rBY) - vAX - (-wA*rAY); + var tY:Number = vBY + (wB*rBX) - vAY - (wA*rAX); + //var vRel:Number = b2Dot(cc.normal, t); + var vRel:Number = cc.normal.x*tX + cc.normal.y*tY; + if (vRel < -b2Settings.b2_velocityThreshold) + { + ccp.velocityBias += -cc.restitution * vRel; + } + } + + // If we have two points, then prepare the block solver. + if (cc.pointCount == 2) + { + var ccp1:b2ContactConstraintPoint = cc.points[0]; + var ccp2:b2ContactConstraintPoint = cc.points[1]; + + var invMassA:Number = bodyA.m_invMass; + var invIA:Number = bodyA.m_invI; + var invMassB:Number = bodyB.m_invMass; + var invIB:Number = bodyB.m_invI; + + //var rn1A:Number = b2Cross(ccp1.rA, normal); + //var rn1B:Number = b2Cross(ccp1.rB, normal); + //var rn2A:Number = b2Cross(ccp2.rA, normal); + //var rn2B:Number = b2Cross(ccp2.rB, normal); + var rn1A:Number = ccp1.rA.x * normalY - ccp1.rA.y * normalX; + var rn1B:Number = ccp1.rB.x * normalY - ccp1.rB.y * normalX; + var rn2A:Number = ccp2.rA.x * normalY - ccp2.rA.y * normalX; + var rn2B:Number = ccp2.rB.x * normalY - ccp2.rB.y * normalX; + + var k11:Number = invMassA + invMassB + invIA * rn1A * rn1A + invIB * rn1B * rn1B; + var k22:Number = invMassA + invMassB + invIA * rn2A * rn2A + invIB * rn2B * rn2B; + var k12:Number = invMassA + invMassB + invIA * rn1A * rn2A + invIB * rn1B * rn2B; + + // Ensure a reasonable condition number. + var k_maxConditionNumber:Number = 100.0; + if ( k11 * k11 < k_maxConditionNumber * (k11 * k22 - k12 * k12)) + { + // K is safe to invert. + cc.K.col1.Set(k11, k12); + cc.K.col2.Set(k12, k22); + cc.K.GetInverse(cc.normalMass); + } + else + { + // The constraints are redundant, just use one. + // TODO_ERIN use deepest? + cc.pointCount = 1; + } + } + } + + //b2Settings.b2Assert(count == m_constraintCount); + } + //~b2ContactSolver(); + + public function InitVelocityConstraints(step: b2TimeStep) : void{ + var tVec:b2Vec2; + var tVec2:b2Vec2; + var tMat:b2Mat22; + + // Warm start. + for (var i:int = 0; i < m_constraintCount; ++i) + { + var c:b2ContactConstraint = m_constraints[ i ]; + + var bodyA:b2Body = c.bodyA; + var bodyB:b2Body = c.bodyB; + var invMassA:Number = bodyA.m_invMass; + var invIA:Number = bodyA.m_invI; + var invMassB:Number = bodyB.m_invMass; + var invIB:Number = bodyB.m_invI; + //var normal:b2Vec2 = new b2Vec2(c.normal.x, c.normal.y); + var normalX:Number = c.normal.x; + var normalY:Number = c.normal.y; + //var tangent:b2Vec2 = b2Math.b2CrossVF(normal, 1.0); + var tangentX:Number = normalY; + var tangentY:Number = -normalX; + + var tX:Number; + + var j:int; + var tCount:int; + if (step.warmStarting) + { + tCount = c.pointCount; + for (j = 0; j < tCount; ++j) + { + var ccp:b2ContactConstraintPoint = c.points[ j ]; + ccp.normalImpulse *= step.dtRatio; + ccp.tangentImpulse *= step.dtRatio; + //b2Vec2 P = ccp->normalImpulse * normal + ccp->tangentImpulse * tangent; + var PX:Number = ccp.normalImpulse * normalX + ccp.tangentImpulse * tangentX; + var PY:Number = ccp.normalImpulse * normalY + ccp.tangentImpulse * tangentY; + + //bodyA.m_angularVelocity -= invIA * b2Math.b2CrossVV(rA, P); + bodyA.m_angularVelocity -= invIA * (ccp.rA.x * PY - ccp.rA.y * PX); + //bodyA.m_linearVelocity.Subtract( b2Math.MulFV(invMassA, P) ); + bodyA.m_linearVelocity.x -= invMassA * PX; + bodyA.m_linearVelocity.y -= invMassA * PY; + //bodyB.m_angularVelocity += invIB * b2Math.b2CrossVV(rB, P); + bodyB.m_angularVelocity += invIB * (ccp.rB.x * PY - ccp.rB.y * PX); + //bodyB.m_linearVelocity.Add( b2Math.MulFV(invMassB, P) ); + bodyB.m_linearVelocity.x += invMassB * PX; + bodyB.m_linearVelocity.y += invMassB * PY; + } + } + else + { + tCount = c.pointCount; + for (j = 0; j < tCount; ++j) + { + var ccp2:b2ContactConstraintPoint = c.points[ j ]; + ccp2.normalImpulse = 0.0; + ccp2.tangentImpulse = 0.0; + } + } + } + } + public function SolveVelocityConstraints() : void{ + var j:int; + var ccp:b2ContactConstraintPoint; + var rAX:Number; + var rAY:Number; + var rBX:Number; + var rBY:Number; + var dvX:Number; + var dvY:Number; + var vn:Number; + var vt:Number; + var lambda:Number; + var maxFriction:Number; + var newImpulse:Number; + var PX:Number; + var PY:Number; + var dX:Number; + var dY:Number; + var P1X:Number; + var P1Y:Number; + var P2X:Number; + var P2Y:Number; + + var tMat:b2Mat22; + var tVec:b2Vec2; + + for (var i:int = 0; i < m_constraintCount; ++i) + { + var c:b2ContactConstraint = m_constraints[ i ]; + var bodyA:b2Body = c.bodyA; + var bodyB:b2Body = c.bodyB; + var wA:Number = bodyA.m_angularVelocity; + var wB:Number = bodyB.m_angularVelocity; + var vA:b2Vec2 = bodyA.m_linearVelocity; + var vB:b2Vec2 = bodyB.m_linearVelocity; + + var invMassA:Number = bodyA.m_invMass; + var invIA:Number = bodyA.m_invI; + var invMassB:Number = bodyB.m_invMass; + var invIB:Number = bodyB.m_invI; + //var normal:b2Vec2 = new b2Vec2(c.normal.x, c.normal.y); + var normalX:Number = c.normal.x; + var normalY:Number = c.normal.y; + //var tangent:b2Vec2 = b2Math.b2CrossVF(normal, 1.0); + var tangentX:Number = normalY; + var tangentY:Number = -normalX; + var friction:Number = c.friction; + + var tX:Number; + + //b2Settings.b2Assert(c.pointCount == 1 || c.pointCount == 2); + // Solve the tangent constraints + for (j = 0; j < c.pointCount; j++) + { + ccp = c.points[j]; + + // Relative velocity at contact + //b2Vec2 dv = vB + b2Cross(wB, ccp->rB) - vA - b2Cross(wA, ccp->rA); + dvX = vB.x - wB * ccp.rB.y - vA.x + wA * ccp.rA.y; + dvY = vB.y + wB * ccp.rB.x - vA.y - wA * ccp.rA.x; + + // Compute tangent force + vt = dvX * tangentX + dvY * tangentY; + lambda = ccp.tangentMass * -vt; + + // b2Clamp the accumulated force + maxFriction = friction * ccp.normalImpulse; + newImpulse = b2Math.Clamp(ccp.tangentImpulse + lambda, -maxFriction, maxFriction); + lambda = newImpulse-ccp.tangentImpulse; + + // Apply contact impulse + PX = lambda * tangentX; + PY = lambda * tangentY; + + vA.x -= invMassA * PX; + vA.y -= invMassA * PY; + wA -= invIA * (ccp.rA.x * PY - ccp.rA.y * PX); + + vB.x += invMassB * PX; + vB.y += invMassB * PY; + wB += invIB * (ccp.rB.x * PY - ccp.rB.y * PX); + + ccp.tangentImpulse = newImpulse; + } + + // Solve the normal constraints + var tCount:int = c.pointCount; + if (c.pointCount == 1) + { + ccp = c.points[ 0 ]; + + // Relative velocity at contact + //b2Vec2 dv = vB + b2Cross(wB, ccp->rB) - vA - b2Cross(wA, ccp->rA); + dvX = vB.x + (-wB * ccp.rB.y) - vA.x - (-wA * ccp.rA.y); + dvY = vB.y + (wB * ccp.rB.x) - vA.y - (wA * ccp.rA.x); + + // Compute normal impulse + //var vn:Number = b2Math.b2Dot(dv, normal); + vn = dvX * normalX + dvY * normalY; + lambda = -ccp.normalMass * (vn - ccp.velocityBias); + + // b2Clamp the accumulated impulse + //newImpulse = b2Math.b2Max(ccp.normalImpulse + lambda, 0.0); + newImpulse = ccp.normalImpulse + lambda; + newImpulse = newImpulse > 0 ? newImpulse : 0.0; + lambda = newImpulse - ccp.normalImpulse; + + // Apply contact impulse + //b2Vec2 P = lambda * normal; + PX = lambda * normalX; + PY = lambda * normalY; + + //vA.Subtract( b2Math.MulFV( invMassA, P ) ); + vA.x -= invMassA * PX; + vA.y -= invMassA * PY; + wA -= invIA * (ccp.rA.x * PY - ccp.rA.y * PX);//invIA * b2Math.b2CrossVV(ccp.rA, P); + + //vB.Add( b2Math.MulFV( invMass2, P ) ); + vB.x += invMassB * PX; + vB.y += invMassB * PY; + wB += invIB * (ccp.rB.x * PY - ccp.rB.y * PX);//invIB * b2Math.b2CrossVV(ccp.rB, P); + + ccp.normalImpulse = newImpulse; + } + else + { + // Block solver developed in collaboration with Dirk Gregorius (back in 01/07 on Box2D_Lite). + // Build the mini LCP for this contact patch + // + // vn = A * x + b, vn >= 0, , vn >= 0, x >= 0 and vn_i * x_i = 0 with i = 1..2 + // + // A = J * W * JT and J = ( -n, -r1 x n, n, r2 x n ) + // b = vn_0 - velocityBias + // + // The system is solved using the "Total enumeration method" (s. Murty). The complementary constraint vn_i * x_i + // implies that we must have in any solution either vn_i = 0 or x_i = 0. So for the 2D contact problem the cases + // vn1 = 0 and vn2 = 0, x1 = 0 and x2 = 0, x1 = 0 and vn2 = 0, x2 = 0 and vn1 = 0 need to be tested. The first valid + // solution that satisfies the problem is chosen. + // + // In order to account of the accumulated impulse 'a' (because of the iterative nature of the solver which only requires + // that the accumulated impulse is clamped and not the incremental impulse) we change the impulse variable (x_i). + // + // Substitute: + // + // x = x' - a + // + // Plug into above equation: + // + // vn = A * x + b + // = A * (x' - a) + b + // = A * x' + b - A * a + // = A * x' + b' + // b' = b - A * a; + + var cp1:b2ContactConstraintPoint = c.points[ 0 ]; + var cp2:b2ContactConstraintPoint = c.points[ 1 ]; + + var aX:Number = cp1.normalImpulse; + var aY:Number = cp2.normalImpulse; + //b2Settings.b2Assert( aX >= 0.0f && aY >= 0.0f ); + + // Relative velocity at contact + //var dv1:b2Vec2 = vB + b2Cross(wB, cp1.rB) - vA - b2Cross(wA, cp1.rA); + var dv1X:Number = vB.x - wB * cp1.rB.y - vA.x + wA * cp1.rA.y; + var dv1Y:Number = vB.y + wB * cp1.rB.x - vA.y - wA * cp1.rA.x; + //var dv2:b2Vec2 = vB + b2Cross(wB, cpB.r2) - vA - b2Cross(wA, cp2.rA); + var dv2X:Number = vB.x - wB * cp2.rB.y - vA.x + wA * cp2.rA.y; + var dv2Y:Number = vB.y + wB * cp2.rB.x - vA.y - wA * cp2.rA.x; + + // Compute normal velocity + //var vn1:Number = b2Dot(dv1, normal); + var vn1:Number = dv1X * normalX + dv1Y * normalY; + //var vn2:Number = b2Dot(dv2, normal); + var vn2:Number = dv2X * normalX + dv2Y * normalY; + + var bX:Number = vn1 - cp1.velocityBias; + var bY:Number = vn2 - cp2.velocityBias; + + //b -= b2Mul(c.K,a); + tMat = c.K; + bX -= tMat.col1.x * aX + tMat.col2.x * aY; + bY -= tMat.col1.y * aX + tMat.col2.y * aY; + + var k_errorTol:Number = 0.001; + for (;; ) + { + // + // Case 1: vn = 0 + // + // 0 = A * x' + b' + // + // Solve for x': + // + // x' = -inv(A) * b' + // + + //var x:b2Vec2 = - b2Mul(c->normalMass, b); + tMat = c.normalMass; + var xX:Number = - (tMat.col1.x * bX + tMat.col2.x * bY); + var xY:Number = - (tMat.col1.y * bX + tMat.col2.y * bY); + + if (xX >= 0.0 && xY >= 0.0) { + // Resubstitute for the incremental impulse + //d = x - a; + dX = xX - aX; + dY = xY - aY; + + //Aply incremental impulse + //P1 = d.x * normal; + P1X = dX * normalX; + P1Y = dX * normalY; + //P2 = d.y * normal; + P2X = dY * normalX; + P2Y = dY * normalY; + + //vA -= invMass1 * (P1 + P2) + vA.x -= invMassA * (P1X + P2X); + vA.y -= invMassA * (P1Y + P2Y); + //wA -= invIA * (b2Cross(cp1.rA, P1) + b2Cross(cp2.rA, P2)); + wA -= invIA * ( cp1.rA.x * P1Y - cp1.rA.y * P1X + cp2.rA.x * P2Y - cp2.rA.y * P2X); + + //vB += invMassB * (P1 + P2) + vB.x += invMassB * (P1X + P2X); + vB.y += invMassB * (P1Y + P2Y); + //wB += invIB * (b2Cross(cp1.rB, P1) + b2Cross(cp2.rB, P2)); + wB += invIB * ( cp1.rB.x * P1Y - cp1.rB.y * P1X + cp2.rB.x * P2Y - cp2.rB.y * P2X); + + // Accumulate + cp1.normalImpulse = xX; + cp2.normalImpulse = xY; + + //#if B2_DEBUG_SOLVER == 1 + // // Post conditions + // //dv1 = vB + b2Cross(wB, cp1.rB) - vA - b2Cross(wA, cp1.rA); + // dv1X = vB.x - wB * cp1.rB.y - vA.x + wA * cp1.rA.y; + // dv1Y = vB.y + wB * cp1.rB.x - vA.y - wA * cp1.rA.x; + // //dv2 = vB + b2Cross(wB, cp2.rB) - vA - b2Cross(wA, cp2.rA); + // dv1X = vB.x - wB * cp2.rB.y - vA.x + wA * cp2.rA.y; + // dv1Y = vB.y + wB * cp2.rB.x - vA.y - wA * cp2.rA.x; + // // Compute normal velocity + // //vn1 = b2Dot(dv1, normal); + // vn1 = dv1X * normalX + dv1Y * normalY; + // //vn2 = b2Dot(dv2, normal); + // vn2 = dv2X * normalX + dv2Y * normalY; + // + // //b2Settings.b2Assert(b2Abs(vn1 - cp1.velocityBias) < k_errorTol); + // //b2Settings.b2Assert(b2Abs(vn2 - cp2.velocityBias) < k_errorTol); + //#endif + break; + } + + // + // Case 2: vn1 = 0 and x2 = 0 + // + // 0 = a11 * x1' + a12 * 0 + b1' + // vn2 = a21 * x1' + a22 * 0 + b2' + // + + xX = - cp1.normalMass * bX; + xY = 0.0; + vn1 = 0.0; + vn2 = c.K.col1.y * xX + bY; + + if (xX >= 0.0 && vn2 >= 0.0) + { + // Resubstitute for the incremental impulse + //d = x - a; + dX = xX - aX; + dY = xY - aY; + + //Aply incremental impulse + //P1 = d.x * normal; + P1X = dX * normalX; + P1Y = dX * normalY; + //P2 = d.y * normal; + P2X = dY * normalX; + P2Y = dY * normalY; + + //vA -= invMassA * (P1 + P2) + vA.x -= invMassA * (P1X + P2X); + vA.y -= invMassA * (P1Y + P2Y); + //wA -= invIA * (b2Cross(cp1.rA, P1) + b2Cross(cp2.rA, P2)); + wA -= invIA * ( cp1.rA.x * P1Y - cp1.rA.y * P1X + cp2.rA.x * P2Y - cp2.rA.y * P2X); + + //vB += invMassB * (P1 + P2) + vB.x += invMassB * (P1X + P2X); + vB.y += invMassB * (P1Y + P2Y); + //wB += invIB * (b2Cross(cp1.rB, P1) + b2Cross(cp2.rB, P2)); + wB += invIB * ( cp1.rB.x * P1Y - cp1.rB.y * P1X + cp2.rB.x * P2Y - cp2.rB.y * P2X); + + // Accumulate + cp1.normalImpulse = xX; + cp2.normalImpulse = xY; + + //#if B2_DEBUG_SOLVER == 1 + // // Post conditions + // //dv1 = vB + b2Cross(wB, cp1.rB) - vA - b2Cross(wA, cp1.rA); + // dv1X = vB.x - wB * cp1.rB.y - vA.x + wA * cp1.rA.y; + // dv1Y = vB.y + wB * cp1.rB.x - vA.y - wA * cp1.rA.x; + // //dv2 = vB + b2Cross(wB, cp2.rB) - vA - b2Cross(wA, cp2.rA); + // dv1X = vB.x - wB * cp2.rB.y - vA.x + wA * cp2.rA.y; + // dv1Y = vB.y + wB * cp2.rB.x - vA.y - wA * cp2.rA.x; + // // Compute normal velocity + // //vn1 = b2Dot(dv1, normal); + // vn1 = dv1X * normalX + dv1Y * normalY; + // //vn2 = b2Dot(dv2, normal); + // vn2 = dv2X * normalX + dv2Y * normalY; + // + // //b2Settings.b2Assert(b2Abs(vn1 - cp1.velocityBias) < k_errorTol); + // //b2Settings.b2Assert(b2Abs(vn2 - cp2.velocityBias) < k_errorTol); + //#endif + break; + } + + // + // Case 3: wB = 0 and x1 = 0 + // + // vn1 = a11 * 0 + a12 * x2' + b1' + // 0 = a21 * 0 + a22 * x2' + b2' + // + + xX = 0.0; + xY = -cp2.normalMass * bY; + vn1 = c.K.col2.x * xY + bX; + vn2 = 0.0; + if (xY >= 0.0 && vn1 >= 0.0) + { + // Resubstitute for the incremental impulse + //d = x - a; + dX = xX - aX; + dY = xY - aY; + + //Aply incremental impulse + //P1 = d.x * normal; + P1X = dX * normalX; + P1Y = dX * normalY; + //P2 = d.y * normal; + P2X = dY * normalX; + P2Y = dY * normalY; + + //vA -= invMassA * (P1 + P2) + vA.x -= invMassA * (P1X + P2X); + vA.y -= invMassA * (P1Y + P2Y); + //wA -= invIA * (b2Cross(cp1.rA, P1) + b2Cross(cp2.rA, P2)); + wA -= invIA * ( cp1.rA.x * P1Y - cp1.rA.y * P1X + cp2.rA.x * P2Y - cp2.rA.y * P2X); + + //vB += invMassB * (P1 + P2) + vB.x += invMassB * (P1X + P2X); + vB.y += invMassB * (P1Y + P2Y); + //wB += invIB * (b2Cross(cp1.rB, P1) + b2Cross(cp2.rB, P2)); + wB += invIB * ( cp1.rB.x * P1Y - cp1.rB.y * P1X + cp2.rB.x * P2Y - cp2.rB.y * P2X); + + // Accumulate + cp1.normalImpulse = xX; + cp2.normalImpulse = xY; + + //#if B2_DEBUG_SOLVER == 1 + // // Post conditions + // //dv1 = vB + b2Cross(wB, cp1.rB) - vA - b2Cross(wA, cp1.rA); + // dv1X = vB.x - wB * cp1.rB.y - vA.x + wA * cp1.rA.y; + // dv1Y = vB.y + wB * cp1.rB.x - vA.y - wA * cp1.rA.x; + // //dv2 = vB + b2Cross(wB, cp2.rB) - vA - b2Cross(wA, cp2.rA); + // dv1X = vB.x - wB * cp2.rB.y - vA.x + wA * cp2.rA.y; + // dv1Y = vB.y + wB * cp2.rB.x - vA.y - wA * cp2.rA.x; + // // Compute normal velocity + // //vn1 = b2Dot(dv1, normal); + // vn1 = dv1X * normalX + dv1Y * normalY; + // //vn2 = b2Dot(dv2, normal); + // vn2 = dv2X * normalX + dv2Y * normalY; + // + // //b2Settings.b2Assert(b2Abs(vn1 - cp1.velocityBias) < k_errorTol); + // //b2Settings.b2Assert(b2Abs(vn2 - cp2.velocityBias) < k_errorTol); + //#endif + break; + } + + // + // Case 4: x1 = 0 and x2 = 0 + // + // vn1 = b1 + // vn2 = b2 + + xX = 0.0; + xY = 0.0; + vn1 = bX; + vn2 = bY; + + if (vn1 >= 0.0 && vn2 >= 0.0 ) { + // Resubstitute for the incremental impulse + //d = x - a; + dX = xX - aX; + dY = xY - aY; + + //Aply incremental impulse + //P1 = d.x * normal; + P1X = dX * normalX; + P1Y = dX * normalY; + //P2 = d.y * normal; + P2X = dY * normalX; + P2Y = dY * normalY; + + //vA -= invMassA * (P1 + P2) + vA.x -= invMassA * (P1X + P2X); + vA.y -= invMassA * (P1Y + P2Y); + //wA -= invIA * (b2Cross(cp1.rA, P1) + b2Cross(cp2.rA, P2)); + wA -= invIA * ( cp1.rA.x * P1Y - cp1.rA.y * P1X + cp2.rA.x * P2Y - cp2.rA.y * P2X); + + //vB += invMassB * (P1 + P2) + vB.x += invMassB * (P1X + P2X); + vB.y += invMassB * (P1Y + P2Y); + //wB += invIB * (b2Cross(cp1.rB, P1) + b2Cross(cp2.rB, P2)); + wB += invIB * ( cp1.rB.x * P1Y - cp1.rB.y * P1X + cp2.rB.x * P2Y - cp2.rB.y * P2X); + + // Accumulate + cp1.normalImpulse = xX; + cp2.normalImpulse = xY; + + //#if B2_DEBUG_SOLVER == 1 + // // Post conditions + // //dv1 = vB + b2Cross(wB, cp1.rB) - vA - b2Cross(wA, cp1.rA); + // dv1X = vB.x - wB * cp1.rB.y - vA.x + wA * cp1.rA.y; + // dv1Y = vB.y + wB * cp1.rB.x - vA.y - wA * cp1.rA.x; + // //dv2 = vB + b2Cross(wB, cp2.rB) - vA - b2Cross(wA, cp2.rA); + // dv1X = vB.x - wB * cp2.rB.y - vA.x + wA * cp2.rA.y; + // dv1Y = vB.y + wB * cp2.rB.x - vA.y - wA * cp2.rA.x; + // // Compute normal velocity + // //vn1 = b2Dot(dv1, normal); + // vn1 = dv1X * normalX + dv1Y * normalY; + // //vn2 = b2Dot(dv2, normal); + // vn2 = dv2X * normalX + dv2Y * normalY; + // + // //b2Settings.b2Assert(b2Abs(vn1 - cp1.velocityBias) < k_errorTol); + // //b2Settings.b2Assert(b2Abs(vn2 - cp2.velocityBias) < k_errorTol); + //#endif + break; + } + + // No solution, give up. This is hit sometimes, but it doesn't seem to matter. + break; + } + } + + + // b2Vec2s in AS3 are copied by reference. The originals are + // references to the same things here and there is no need to + // copy them back, unlike in C++ land where b2Vec2s are + // copied by value. + /*bodyA->m_linearVelocity = vA; + bodyB->m_linearVelocity = vB;*/ + bodyA.m_angularVelocity = wA; + bodyB.m_angularVelocity = wB; + } + } + + public function FinalizeVelocityConstraints() : void + { + for (var i:int = 0; i < m_constraintCount; ++i) + { + var c:b2ContactConstraint = m_constraints[ i ]; + var m:b2Manifold = c.manifold; + + for (var j:int = 0; j < c.pointCount; ++j) + { + var point1:b2ManifoldPoint = m.m_points[j]; + var point2:b2ContactConstraintPoint = c.points[j]; + point1.m_normalImpulse = point2.normalImpulse; + point1.m_tangentImpulse = point2.tangentImpulse; + } + } + } + +//#if 1 +// Sequential solver +// public function SolvePositionConstraints(baumgarte:Number):Boolean{ +// var minSeparation:Number = 0.0; +// +// var tMat:b2Mat22; +// var tVec:b2Vec2; +// +// for (var i:int = 0; i < m_constraintCount; ++i) +// { +// var c:b2ContactConstraint = m_constraints[ i ]; +// var bodyA:b2Body = c.bodyA; +// var bodyB:b2Body = c.bodyB; +// var bA_sweep_c:b2Vec2 = bodyA.m_sweep.c; +// var bA_sweep_a:Number = bodyA.m_sweep.a; +// var bB_sweep_c:b2Vec2 = bodyB.m_sweep.c; +// var bB_sweep_a:Number = bodyB.m_sweep.a; +// +// var invMassa:Number = bodyA.m_mass * bodyA.m_invMass; +// var invIa:Number = bodyA.m_mass * bodyA.m_invI; +// var invMassb:Number = bodyB.m_mass * bodyB.m_invMass; +// var invIb:Number = bodyB.m_mass * bodyB.m_invI; +// //var normal:b2Vec2 = new b2Vec2(c.normal.x, c.normal.y); +// var normalX:Number = c.normal.x; +// var normalY:Number = c.normal.y; +// +// // Solver normal constraints +// var tCount:int = c.pointCount; +// for (var j:int = 0; j < tCount; ++j) +// { +// var ccp:b2ContactConstraintPoint = c.points[ j ]; +// +// //r1 = b2Mul(bodyA->m_xf.R, ccp->localAnchor1 - bodyA->GetLocalCenter()); +// tMat = bodyA.m_xf.R; +// tVec = bodyA.m_sweep.localCenter; +// var r1X:Number = ccp.localAnchor1.x - tVec.x; +// var r1Y:Number = ccp.localAnchor1.y - tVec.y; +// tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); +// r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); +// r1X = tX; +// +// //r2 = b2Mul(bodyB->m_xf.R, ccp->localAnchor2 - bodyB->GetLocalCenter()); +// tMat = bodyB.m_xf.R; +// tVec = bodyB.m_sweep.localCenter; +// var r2X:Number = ccp.localAnchor2.x - tVec.x; +// var r2Y:Number = ccp.localAnchor2.y - tVec.y; +// var tX:Number = (tMat.col1.x * r2X + tMat.col2.x * r2Y); +// r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); +// r2X = tX; +// +// //b2Vec2 p1 = bodyA->m_sweep.c + r1; +// var p1X:Number = b1_sweep_c.x + r1X; +// var p1Y:Number = b1_sweep_c.y + r1Y; +// +// //b2Vec2 p2 = bodyB->m_sweep.c + r2; +// var p2X:Number = b2_sweep_c.x + r2X; +// var p2Y:Number = b2_sweep_c.y + r2Y; +// +// //var dp:b2Vec2 = b2Math.SubtractVV(p2, p1); +// var dpX:Number = p2X - p1X; +// var dpY:Number = p2Y - p1Y; +// +// // Approximate the current separation. +// //var separation:Number = b2Math.b2Dot(dp, normal) + ccp.separation; +// var separation:Number = (dpX*normalX + dpY*normalY) + ccp.separation; +// +// // Track max constraint error. +// minSeparation = b2Math.b2Min(minSeparation, separation); +// +// // Prevent large corrections and allow slop. +// var C:Number = b2Math.b2Clamp(baumgarte * (separation + b2Settings.b2_linearSlop), -b2Settings.b2_maxLinearCorrection, 0.0); +// +// // Compute normal impulse +// var dImpulse:Number = -ccp.equalizedMass * C; +// +// //var P:b2Vec2 = b2Math.MulFV( dImpulse, normal ); +// var PX:Number = dImpulse * normalX; +// var PY:Number = dImpulse * normalY; +// +// //bodyA.m_position.Subtract( b2Math.MulFV( invMass1, impulse ) ); +// b1_sweep_c.x -= invMass1 * PX; +// b1_sweep_c.y -= invMass1 * PY; +// b1_sweep_a -= invI1 * (r1X * PY - r1Y * PX);//b2Math.b2CrossVV(r1, P); +// bodyA.m_sweep.a = b1_sweep_a; +// bodyA.SynchronizeTransform(); +// +// //bodyB.m_position.Add( b2Math.MulFV( invMass2, P ) ); +// b2_sweep_c.x += invMass2 * PX; +// b2_sweep_c.y += invMass2 * PY; +// b2_sweep_a += invI2 * (r2X * PY - r2Y * PX);//b2Math.b2CrossVV(r2, P); +// bodyB.m_sweep.a = b2_sweep_a; +// bodyB.SynchronizeTransform(); +// } +// // Update body rotations +// //bodyA.m_sweep.a = b1_sweep_a; +// //bodyB.m_sweep.a = b2_sweep_a; +// } +// +// // We can't expect minSpeparation >= -b2_linearSlop because we don't +// // push the separation above -b2_linearSlop. +// return minSeparation >= -1.5 * b2Settings.b2_linearSlop; +// } +//#else + // Sequential solver. + private static var s_psm:b2PositionSolverManifold = new b2PositionSolverManifold(); + public function SolvePositionConstraints(baumgarte:Number):Boolean + { + var minSeparation:Number = 0.0; + + for (var i:int = 0; i < m_constraintCount; i++) + { + var c:b2ContactConstraint = m_constraints[i]; + var bodyA:b2Body = c.bodyA; + var bodyB:b2Body = c.bodyB; + + var invMassA:Number = bodyA.m_mass * bodyA.m_invMass; + var invIA:Number = bodyA.m_mass * bodyA.m_invI; + var invMassB:Number = bodyB.m_mass * bodyB.m_invMass; + var invIB:Number = bodyB.m_mass * bodyB.m_invI; + + + s_psm.Initialize(c); + var normal:b2Vec2 = s_psm.m_normal; + + // Solve normal constraints + for (var j:int = 0; j < c.pointCount; j++) + { + var ccp:b2ContactConstraintPoint = c.points[j]; + + var point:b2Vec2 = s_psm.m_points[j]; + var separation:Number = s_psm.m_separations[j]; + + var rAX:Number = point.x - bodyA.m_sweep.c.x; + var rAY:Number = point.y - bodyA.m_sweep.c.y; + var rBX:Number = point.x - bodyB.m_sweep.c.x; + var rBY:Number = point.y - bodyB.m_sweep.c.y; + + // Track max constraint error. + minSeparation = minSeparation < separation?minSeparation:separation; + + // Prevent large corrections and allow slop. + var C:Number = b2Math.Clamp(baumgarte * (separation + b2Settings.b2_linearSlop), -b2Settings.b2_maxLinearCorrection, 0.0); + + // Compute normal impulse + var impulse:Number = -ccp.equalizedMass * C; + + var PX:Number = impulse * normal.x; + var PY:Number = impulse * normal.y; + + //bodyA.m_sweep.c -= invMassA * P; + bodyA.m_sweep.c.x -= invMassA * PX; + bodyA.m_sweep.c.y -= invMassA * PY; + //bodyA.m_sweep.a -= invIA * b2Cross(rA, P); + bodyA.m_sweep.a -= invIA * (rAX * PY - rAY * PX); + bodyA.SynchronizeTransform(); + + //bodyB.m_sweep.c += invMassB * P; + bodyB.m_sweep.c.x += invMassB * PX; + bodyB.m_sweep.c.y += invMassB * PY; + //bodyB.m_sweep.a += invIB * b2Cross(rB, P); + bodyB.m_sweep.a += invIB * (rBX * PY - rBY * PX); + bodyB.SynchronizeTransform(); + } + } + + // We can't expect minSpeparation >= -b2_linearSlop because we don't + // push the separation above -b2_linearSlop. + return minSeparation > -1.5 * b2Settings.b2_linearSlop; + } + +//#endif + private var m_step:b2TimeStep = new b2TimeStep(); + private var m_allocator:*; + b2internal var m_constraints:Vector. = new Vector. (); + private var m_constraintCount:int; +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2EdgeAndCircleContact.as b/srclib/Box2D/Dynamics/Contacts/b2EdgeAndCircleContact.as new file mode 100644 index 00000000..b31ab940 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2EdgeAndCircleContact.as @@ -0,0 +1,173 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + + import Box2D.Collision.*; + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; +use namespace b2internal; + +/** +* @private +*/ +public class b2EdgeAndCircleContact extends b2Contact +{ + static public function Create(allocator:*):b2Contact{ + return new b2EdgeAndCircleContact(); + } + static public function Destroy(contact:b2Contact, allocator:*) : void{ + // + } + + public function Reset(fixtureA:b2Fixture, fixtureB:b2Fixture):void{ + super.Reset(fixtureA, fixtureB); + //b2Settings.b2Assert(m_shape1.m_type == b2Shape.e_circleShape); + //b2Settings.b2Assert(m_shape2.m_type == b2Shape.e_circleShape); + } + //~b2EdgeAndCircleContact() {} + + b2internal override function Evaluate() : void{ + var bA:b2Body = m_fixtureA.GetBody(); + var bB:b2Body = m_fixtureB.GetBody(); + b2CollideEdgeAndCircle(m_manifold, + m_fixtureA.GetShape() as b2EdgeShape, bA.m_xf, + m_fixtureB.GetShape() as b2CircleShape, bB.m_xf); + } + + private function b2CollideEdgeAndCircle(manifold: b2Manifold, + edge: b2EdgeShape, + xf1: b2Transform, + circle: b2CircleShape, + xf2: b2Transform): void + { + //TODO_BORIS + /* + manifold.m_pointCount = 0; + var tMat: b2Mat22; + var tVec: b2Vec2; + var dX: Number; + var dY: Number; + var tX: Number; + var tY: Number; + var tPoint:b2ManifoldPoint; + + //b2Vec2 c = b2Mul(xf2, circle->GetLocalPosition()); + tMat = xf2.R; + tVec = circle.m_r; + var cX: Number = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var cY: Number = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + //b2Vec2 cLocal = b2MulT(xf1, c); + tMat = xf1.R; + tX = cX - xf1.position.x; + tY = cY - xf1.position.y; + var cLocalX: Number = (tX * tMat.col1.x + tY * tMat.col1.y ); + var cLocalY: Number = (tX * tMat.col2.x + tY * tMat.col2.y ); + + var n: b2Vec2 = edge.m_normal; + var v1: b2Vec2 = edge.m_v1; + var v2: b2Vec2 = edge.m_v2; + var radius: Number = circle.m_radius; + var separation: Number; + + var dirDist: Number = (cLocalX - v1.x) * edge.m_direction.x + + (cLocalY - v1.y) * edge.m_direction.y; + + var normalCalculated: Boolean = false; + + if (dirDist <= 0) { + dX = cLocalX - v1.x; + dY = cLocalY - v1.y; + if (dX * edge.m_cornerDir1.x + dY * edge.m_cornerDir1.y < 0) { + return; + } + dX = cX - (xf1.position.x + (tMat.col1.x * v1.x + tMat.col2.x * v1.y)); + dY = cY - (xf1.position.y + (tMat.col1.y * v1.x + tMat.col2.y * v1.y)); + } else if (dirDist >= edge.m_length) { + dX = cLocalX - v2.x; + dY = cLocalY - v2.y; + if (dX * edge.m_cornerDir2.x + dY * edge.m_cornerDir2.y > 0) { + return; + } + dX = cX - (xf1.position.x + (tMat.col1.x * v2.x + tMat.col2.x * v2.y)); + dY = cY - (xf1.position.y + (tMat.col1.y * v2.x + tMat.col2.y * v2.y)); + } else { + separation = (cLocalX - v1.x) * n.x + (cLocalY - v1.y) * n.y; + if (separation > radius || separation < -radius) { + return; + } + separation -= radius; + + //manifold.normal = b2Mul(xf1.R, n); + tMat = xf1.R; + manifold.normal.x = (tMat.col1.x * n.x + tMat.col2.x * n.y); + manifold.normal.y = (tMat.col1.y * n.x + tMat.col2.y * n.y); + + normalCalculated = true; + } + + if (!normalCalculated) { + var distSqr: Number = dX * dX + dY * dY; + if (distSqr > radius * radius) + { + return; + } + + if (distSqr < Number.MIN_VALUE) + { + separation = -radius; + manifold.normal.x = (tMat.col1.x * n.x + tMat.col2.x * n.y); + manifold.normal.y = (tMat.col1.y * n.x + tMat.col2.y * n.y); + } + else + { + distSqr = Math.sqrt(distSqr); + dX /= distSqr; + dY /= distSqr; + separation = distSqr - radius; + manifold.normal.x = dX; + manifold.normal.y = dY; + } + } + + tPoint = manifold.points[0]; + manifold.pointCount = 1; + tPoint.id.key = 0; + tPoint.separation = separation; + cX = cX - radius * manifold.normal.x; + cY = cY - radius * manifold.normal.y; + + tX = cX - xf1.position.x; + tY = cY - xf1.position.y; + tPoint.localPoint1.x = (tX * tMat.col1.x + tY * tMat.col1.y ); + tPoint.localPoint1.y = (tX * tMat.col2.x + tY * tMat.col2.y ); + + tMat = xf2.R; + tX = cX - xf2.position.x; + tY = cY - xf2.position.y; + tPoint.localPoint2.x = (tX * tMat.col1.x + tY * tMat.col1.y ); + tPoint.localPoint2.y = (tX * tMat.col2.x + tY * tMat.col2.y ); + */ + } +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2NullContact.as b/srclib/Box2D/Dynamics/Contacts/b2NullContact.as new file mode 100644 index 00000000..51364af6 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2NullContact.as @@ -0,0 +1,35 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + import Box2D.Common.b2internal; + +use namespace b2internal; + + +/** +* @private +*/ +public class b2NullContact extends b2Contact +{ + public function b2NullContact() {} + b2internal override function Evaluate(): void {} +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2PolyAndCircleContact.as b/srclib/Box2D/Dynamics/Contacts/b2PolyAndCircleContact.as new file mode 100644 index 00000000..70b0e571 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2PolyAndCircleContact.as @@ -0,0 +1,57 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + + import Box2D.Collision.*; + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Dynamics.*; +use namespace b2internal; + + +/** +* @private +*/ +public class b2PolyAndCircleContact extends b2Contact{ + + static public function Create(allocator:*):b2Contact{ + return new b2PolyAndCircleContact(); + } + static public function Destroy(contact:b2Contact, allocator:*): void{ + } + + public function Reset(fixtureA:b2Fixture, fixtureB:b2Fixture):void{ + super.Reset(fixtureA, fixtureB); + b2Settings.b2Assert(fixtureA.GetType() == b2Shape.e_polygonShape); + b2Settings.b2Assert(fixtureB.GetType() == b2Shape.e_circleShape); + } + //~b2PolyAndCircleContact() {} + + b2internal override function Evaluate(): void{ + var bA:b2Body = m_fixtureA.m_body; + var bB:b2Body = m_fixtureB.m_body; + + b2Collision.CollidePolygonAndCircle(m_manifold, + m_fixtureA.GetShape() as b2PolygonShape, bA.m_xf, + m_fixtureB.GetShape() as b2CircleShape, bB.m_xf); + } +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2PolyAndEdgeContact.as b/srclib/Box2D/Dynamics/Contacts/b2PolyAndEdgeContact.as new file mode 100644 index 00000000..0ccac843 --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2PolyAndEdgeContact.as @@ -0,0 +1,372 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + + import Box2D.Collision.*; + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; +use namespace b2internal; + + +/** +* @private +*/ +public class b2PolyAndEdgeContact extends b2Contact{ + + static public function Create(allocator:*):b2Contact{ + return new b2PolyAndEdgeContact(); + } + static public function Destroy(contact:b2Contact, allocator:*): void{ + } + + public function Reset(fixtureA:b2Fixture, fixtureB:b2Fixture):void{ + super.Reset(fixtureA, fixtureB); + b2Settings.b2Assert(fixtureA.GetType() == b2Shape.e_polygonShape); + b2Settings.b2Assert(fixtureB.GetType() == b2Shape.e_edgeShape); + } + //~b2PolyAndEdgeContact() {} + + b2internal override function Evaluate(): void{ + var bA:b2Body = m_fixtureA.GetBody(); + var bB:b2Body = m_fixtureB.GetBody(); + + b2CollidePolyAndEdge(m_manifold, + m_fixtureA.GetShape() as b2PolygonShape, bA.m_xf, + m_fixtureB.GetShape() as b2EdgeShape, bB.m_xf); + } + + private function b2CollidePolyAndEdge(manifold: b2Manifold, + polygon: b2PolygonShape, + xf1: b2Transform, + edge: b2EdgeShape, + xf2: b2Transform): void + { + //TODO_BORIS + /* + manifold.pointCount = 0; + var tMat: b2Mat22; + var tVec1: b2Vec2; + var tVec2: b2Vec2; + var tX: Number; + var tY: Number; + var tPoint:b2ManifoldPoint; + var ratio: Number; + + //b2Vec2 v1 = b2Mul(xf2, edge->GetVertex1()); + tMat = xf2.R; + tVec1 = edge.m_v1; + var v1X: Number = xf2.position.x + (tMat.col1.x * tVec1.x + tMat.col2.x * tVec1.y); + var v1Y: Number = xf2.position.y + (tMat.col1.y * tVec1.x + tMat.col2.y * tVec1.y); + + //b2Vec2 v2 = b2Mul(xf2, edge->GetVertex2()); + tVec1 = edge.m_v2; + var v2X: Number = xf2.position.x + (tMat.col1.x * tVec1.x + tMat.col2.x * tVec1.y); + var v2Y: Number = xf2.position.y + (tMat.col1.y * tVec1.x + tMat.col2.y * tVec1.y); + + //b2Vec2 n = b2Mul(xf2.R, edge->GetNormalVector()); + tVec1 = edge.m_normal; + var nX: Number = (tMat.col1.x * tVec1.x + tMat.col2.x * tVec1.y); + var nY: Number = (tMat.col1.y * tVec1.x + tMat.col2.y * tVec1.y); + + //b2Vec2 v1Local = b2MulT(xf1, v1); + tMat = xf1.R; + tX = v1X - xf1.position.x; + tY = v1Y - xf1.position.y; + var v1LocalX: Number = (tX * tMat.col1.x + tY * tMat.col1.y ); + var v1LocalY: Number = (tX * tMat.col2.x + tY * tMat.col2.y ); + + //b2Vec2 v2Local = b2MulT(xf1, v2); + tX = v2X - xf1.position.x; + tY = v2Y - xf1.position.y; + var v2LocalX: Number = (tX * tMat.col1.x + tY * tMat.col1.y ); + var v2LocalY: Number = (tX * tMat.col2.x + tY * tMat.col2.y ); + + //b2Vec2 nLocal = b2MulT(xf1.R, n); + var nLocalX: Number = (nX * tMat.col1.x + nY * tMat.col1.y ); + var nLocalY: Number = (nX * tMat.col2.x + nY * tMat.col2.y ); + + var separation1: Number; + var separationIndex1: int = -1; // which normal on the poly found the shallowest depth? + var separationMax1: Number = -Number.MAX_VALUE; // the shallowest depth of edge in poly + var separation2: Number; + var separationIndex2: int = -1; // which normal on the poly found the shallowest depth? + var separationMax2: Number = -Number.MAX_VALUE; // the shallowest depth of edge in poly + var separationMax: Number = -Number.MAX_VALUE; // the shallowest depth of edge in poly + var separationV1: Boolean = false; // is the shallowest depth from edge's v1 or v2 vertex? + var separationIndex: int = -1; // which normal on the poly found the shallowest depth? + + var vertexCount: int = polygon.m_vertexCount; + var vertices: Array = polygon.m_vertices; + var normals: Array = polygon.m_normals; + + var enterStartIndex: int = -1; // the last poly vertex above the edge + var enterEndIndex: int = -1; // the first poly vertex below the edge + var exitStartIndex: int = -1; // the last poly vertex below the edge + var exitEndIndex: int = -1; // the first poly vertex above the edge + + // the "N" in the following variables refers to the edge's normal. + // these are projections of poly vertices along the edge's normal, + // a.k.a. they are the separation of the poly from the edge. + var prevSepN: Number = 0.0; + var nextSepN: Number = 0.0; + var enterSepN: Number = 0.0; // the depth of enterEndIndex under the edge (stored as a separation, so it's negative) + var exitSepN: Number = 0.0; // the depth of exitStartIndex under the edge (stored as a separation, so it's negative) + var deepestSepN: Number = Number.MAX_VALUE; // the depth of the deepest poly vertex under the end (stored as a separation, so it's negative) + + + // for each poly normal, get the edge's depth into the poly. + // for each poly vertex, get the vertex's depth into the edge. + // use these calculations to define the remaining variables declared above. + tVec1 = vertices[vertexCount-1]; + prevSepN = (tVec1.x - v1LocalX) * nLocalX + (tVec1.y - v1LocalY) * nLocalY; + for (var i: int = 0; i < vertexCount; i++) + { + tVec1 = vertices[i]; + tVec2 = normals[i]; + separation1 = (v1LocalX - tVec1.x) * tVec2.x + (v1LocalY - tVec1.y) * tVec2.y; + separation2 = (v2LocalX - tVec1.x) * tVec2.x + (v2LocalY - tVec1.y) * tVec2.y; + if (separation2 < separation1) { + if (separation2 > separationMax) { + separationMax = separation2; + separationV1 = false; + separationIndex = i; + } + } else { + if (separation1 > separationMax) { + separationMax = separation1; + separationV1 = true; + separationIndex = i; + } + } + if (separation1 > separationMax1) { + separationMax1 = separation1; + separationIndex1 = i; + } + if (separation2 > separationMax2) { + separationMax2 = separation2; + separationIndex2 = i; + } + + nextSepN = (tVec1.x - v1LocalX) * nLocalX + (tVec1.y - v1LocalY) * nLocalY; + if (nextSepN >= 0.0 && prevSepN < 0.0) { + exitStartIndex = (i == 0) ? vertexCount-1 : i-1; + exitEndIndex = i; + exitSepN = prevSepN; + } else if (nextSepN < 0.0 && prevSepN >= 0.0) { + enterStartIndex = (i == 0) ? vertexCount-1 : i-1; + enterEndIndex = i; + enterSepN = nextSepN; + } + if (nextSepN < deepestSepN) { + deepestSepN = nextSepN; + } + prevSepN = nextSepN; + } + + if (enterStartIndex == -1) { + // poly is entirely below or entirely above edge, return with no contact: + return; + } + if (separationMax > 0.0) { + // poly is laterally disjoint with edge, return with no contact: + return; + } + + // if the poly is near a convex corner on the edge + if ((separationV1 && edge.m_cornerConvex1) || (!separationV1 && edge.m_cornerConvex2)) { + // if shallowest depth was from edge into poly, + // use the edge's vertex as the contact point: + if (separationMax > deepestSepN + b2Settings.b2_linearSlop) { + // if -normal angle is closer to adjacent edge than this edge, + // let the adjacent edge handle it and return with no contact: + if (separationV1) { + tMat = xf2.R; + tVec1 = edge.m_cornerDir1; + tX = (tMat.col1.x * tVec1.x + tMat.col2.x * tVec1.y); + tY = (tMat.col1.y * tVec1.x + tMat.col2.y * tVec1.y); + tMat = xf1.R; + v1X = (tMat.col1.x * tX + tMat.col2.x * tY); // note abuse of v1... + v1Y = (tMat.col1.y * tX + tMat.col2.y * tY); + tVec2 = normals[separationIndex1]; + if (tVec2.x * v1X + tVec2.y * v1Y >= 0.0) { + return; + } + } else { + tMat = xf2.R; + tVec1 = edge.m_cornerDir2; + tX = (tMat.col1.x * tVec1.x + tMat.col2.x * tVec1.y); + tY = (tMat.col1.y * tVec1.x + tMat.col2.y * tVec1.y); + tMat = xf1.R; + v1X = (tMat.col1.x * tX + tMat.col2.x * tY); // note abuse of v1... + v1Y = (tMat.col1.y * tX + tMat.col2.y * tY); + tVec2 = normals[separationIndex2]; + if (tVec2.x * v1X + tVec2.y * v1Y <= 0.0) { + return; + } + } + + tPoint = manifold.points[0]; + manifold.pointCount = 1; + + //manifold->normal = b2Mul(xf1.R, normals[separationIndex]); + tMat = xf1.R; + tVec2 = normals[separationIndex]; + manifold.normal.x = (tMat.col1.x * tVec2.x + tMat.col2.x * tVec2.y); + manifold.normal.y = (tMat.col1.y * tVec2.x + tMat.col2.y * tVec2.y); + + tPoint.separation = separationMax; + tPoint.id.features.incidentEdge = separationIndex; + tPoint.id.features.incidentVertex = b2Collision.b2_nullFeature; + tPoint.id.features.referenceEdge = 0; + tPoint.id.features.flip = 0; + if (separationV1) { + tPoint.localPoint1.x = v1LocalX; + tPoint.localPoint1.y = v1LocalY; + tPoint.localPoint2.x = edge.m_v1.x; + tPoint.localPoint2.y = edge.m_v1.y; + } else { + tPoint.localPoint1.x = v2LocalX; + tPoint.localPoint1.y = v2LocalY; + tPoint.localPoint2.x = edge.m_v2.x; + tPoint.localPoint2.y = edge.m_v2.y; + } + return; + } + } + + // We're going to use the edge's normal now. + manifold.normal.x = -nX; + manifold.normal.y = -nY; + + // Check whether we only need one contact point. + if (enterEndIndex == exitStartIndex) { + tPoint = manifold.points[0]; + manifold.pointCount = 1; + tPoint.id.features.incidentEdge = enterEndIndex; + tPoint.id.features.incidentVertex = b2Collision.b2_nullFeature; + tPoint.id.features.referenceEdge = 0; + tPoint.id.features.flip = 0; + tVec1 = vertices[enterEndIndex]; + tPoint.localPoint1.x = tVec1.x; + tPoint.localPoint1.y = tVec1.y; + + tMat = xf1.R; + tX = xf1.position.x + (tMat.col1.x * tVec1.x + tMat.col2.x * tVec1.y) - xf2.position.x; + tY = xf1.position.y + (tMat.col1.y * tVec1.x + tMat.col2.y * tVec1.y) - xf2.position.y; + tMat = xf2.R; + tPoint.localPoint2.x = (tX * tMat.col1.x + tY * tMat.col1.y ); + tPoint.localPoint2.y = (tX * tMat.col2.x + tY * tMat.col2.y ); + + tPoint.separation = enterSepN; + return; + } + + manifold.pointCount = 2; + + // the edge's direction vector, but in the frame of the polygon: + tX = -nLocalY; + tY = nLocalX; + + tVec1 = vertices[enterEndIndex]; + var dirProj1: Number = tX * (tVec1.x - v1LocalX) + tY * (tVec1.y - v1LocalY); + var dirProj2: Number; + + // The contact resolution is more robust if the two manifold points are + // adjacent to each other on the polygon. So pick the first two poly + // vertices that are under the edge: + exitEndIndex = (enterEndIndex == vertexCount - 1) ? 0 : enterEndIndex + 1; + tVec1 = vertices[exitStartIndex]; + if (exitEndIndex != exitStartIndex) { + exitStartIndex = exitEndIndex; + + exitSepN = nLocalX * (tVec1.x - v1LocalX) + nLocalY * (tVec1.y - v1LocalY); + } + dirProj2 = tX * (tVec1.x - v1LocalX) + tY * (tVec1.y - v1LocalY); + + tPoint = manifold.points[0]; + tPoint.id.features.incidentEdge = enterEndIndex; + tPoint.id.features.incidentVertex = b2Collision.b2_nullFeature; + tPoint.id.features.referenceEdge = 0; + tPoint.id.features.flip = 0; + + if (dirProj1 > edge.m_length) { + tPoint.localPoint1.x = v2LocalX; + tPoint.localPoint1.y = v2LocalY; + tPoint.localPoint2.x = edge.m_v2.x; + tPoint.localPoint2.y = edge.m_v2.y; + ratio = (edge.m_length - dirProj2) / (dirProj1 - dirProj2); + if (ratio > 100.0 * Number.MIN_VALUE && ratio < 1.0) { + tPoint.separation = exitSepN * (1.0 - ratio) + enterSepN * ratio; + } else { + tPoint.separation = enterSepN; + } + } else { + tVec1 = vertices[enterEndIndex]; + tPoint.localPoint1.x = tVec1.x; + tPoint.localPoint1.y = tVec1.y; + + tMat = xf1.R; + tX = xf1.position.x + (tMat.col1.x * tVec1.x + tMat.col2.x * tVec1.y) - xf2.position.x; + tY = xf1.position.y + (tMat.col1.y * tVec1.x + tMat.col2.y * tVec1.y) - xf2.position.y; + tMat = xf2.R; + tPoint.localPoint2.x = (tX * tMat.col1.x + tY * tMat.col1.y); + tPoint.localPoint2.y = (tX * tMat.col2.x + tY * tMat.col2.y); + + tPoint.separation = enterSepN; + } + + tPoint = manifold.points[1]; + tPoint.id.features.incidentEdge = exitStartIndex; + tPoint.id.features.incidentVertex = b2Collision.b2_nullFeature; + tPoint.id.features.referenceEdge = 0; + tPoint.id.features.flip = 0; + + if (dirProj2 < 0.0) { + tPoint.localPoint1.x = v1LocalX; + tPoint.localPoint1.y = v1LocalY; + tPoint.localPoint2.x = edge.m_v1.x; + tPoint.localPoint2.y = edge.m_v1.y; + ratio = (-dirProj1) / (dirProj2 - dirProj1); + if (ratio > 100.0 * Number.MIN_VALUE && ratio < 1.0) { + tPoint.separation = enterSepN * (1.0 - ratio) + exitSepN * ratio; + } else { + tPoint.separation = exitSepN; + } + } else { + tVec1 = vertices[exitStartIndex]; + tPoint.localPoint1.x = tVec1.x; + tPoint.localPoint1.y = tVec1.y; + + tMat = xf1.R; + tX = xf1.position.x + (tMat.col1.x * tVec1.x + tMat.col2.x * tVec1.y) - xf2.position.x; + tY = xf1.position.y + (tMat.col1.y * tVec1.x + tMat.col2.y * tVec1.y) - xf2.position.y; + tMat = xf2.R; + tPoint.localPoint2.x = (tX * tMat.col1.x + tY * tMat.col1.y); + tPoint.localPoint2.y = (tX * tMat.col2.x + tY * tMat.col2.y); + + tPoint.separation = exitSepN; + } + */ + } +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Contacts/b2PolygonContact.as b/srclib/Box2D/Dynamics/Contacts/b2PolygonContact.as new file mode 100644 index 00000000..220c50dd --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2PolygonContact.as @@ -0,0 +1,60 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts{ + + + import Box2D.Collision.*; + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Dynamics.*; +use namespace b2internal; + + +/** +* @private +*/ +public class b2PolygonContact extends b2Contact +{ + static public function Create(allocator:*):b2Contact{ + //void* mem = allocator->Allocate(sizeof(b2PolyContact)); + return new b2PolygonContact(); + } + static public function Destroy(contact:b2Contact, allocator:*): void{ + //((b2PolyContact*)contact)->~b2PolyContact(); + //allocator->Free(contact, sizeof(b2PolyContact)); + } + + public function Reset(fixtureA:b2Fixture, fixtureB:b2Fixture): void{ + super.Reset(fixtureA, fixtureB); + //b2Settings.b2Assert(m_shape1.m_type == b2Shape.e_polygonShape); + //b2Settings.b2Assert(m_shape2.m_type == b2Shape.e_polygonShape); + } + //~b2PolyContact() {} + + b2internal override function Evaluate(): void{ + var bA:b2Body = m_fixtureA.GetBody(); + var bB:b2Body = m_fixtureB.GetBody(); + + b2Collision.CollidePolygons(m_manifold, + m_fixtureA.GetShape() as b2PolygonShape, bA.m_xf, + m_fixtureB.GetShape() as b2PolygonShape, bB.m_xf); + } +}; + +} diff --git a/srclib/Box2D/Dynamics/Contacts/b2PositionSolverManifold.as b/srclib/Box2D/Dynamics/Contacts/b2PositionSolverManifold.as new file mode 100644 index 00000000..e0b2071f --- /dev/null +++ b/srclib/Box2D/Dynamics/Contacts/b2PositionSolverManifold.as @@ -0,0 +1,151 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Contacts +{ + + import Box2D.Collision.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; + +use namespace b2internal; + +internal class b2PositionSolverManifold +{ + public function b2PositionSolverManifold() + { + m_normal = new b2Vec2(); + m_separations = new Vector.(b2Settings.b2_maxManifoldPoints); + m_points = new Vector.(b2Settings.b2_maxManifoldPoints); + for (var i:int = 0; i < b2Settings.b2_maxManifoldPoints; i++) + { + m_points[i] = new b2Vec2(); + } + } + + private static var circlePointA:b2Vec2 = new b2Vec2(); + private static var circlePointB:b2Vec2 = new b2Vec2(); + public function Initialize(cc:b2ContactConstraint):void + { + b2Settings.b2Assert(cc.pointCount > 0); + + var i:int; + var clipPointX:Number; + var clipPointY:Number; + var tMat:b2Mat22; + var tVec:b2Vec2; + var planePointX:Number; + var planePointY:Number; + + switch(cc.type) + { + case b2Manifold.e_circles: + { + //var pointA:b2Vec2 = cc.bodyA.GetWorldPoint(cc.localPoint); + tMat = cc.bodyA.m_xf.R; + tVec = cc.localPoint; + var pointAX:Number = cc.bodyA.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var pointAY:Number = cc.bodyA.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //var pointB:b2Vec2 = cc.bodyB.GetWorldPoint(cc.points[0].localPoint); + tMat = cc.bodyB.m_xf.R; + tVec = cc.points[0].localPoint; + var pointBX:Number = cc.bodyB.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + var pointBY:Number = cc.bodyB.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + var dX:Number = pointBX - pointAX; + var dY:Number = pointBY - pointAY; + var d2:Number = dX * dX + dY * dY; + if (d2 > Number.MIN_VALUE*Number.MIN_VALUE) + { + var d:Number = Math.sqrt(d2); + m_normal.x = dX/d; + m_normal.y = dY/d; + } + else + { + m_normal.x = 1.0; + m_normal.y = 0.0; + } + m_points[0].x = 0.5 * (pointAX + pointBX); + m_points[0].y = 0.5 * (pointAY + pointBY); + m_separations[0] = dX * m_normal.x + dY * m_normal.y - cc.radius; + } + break; + case b2Manifold.e_faceA: + { + //m_normal = cc.bodyA.GetWorldVector(cc.localPlaneNormal); + tMat = cc.bodyA.m_xf.R; + tVec = cc.localPlaneNormal; + m_normal.x = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + m_normal.y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //planePoint = cc.bodyA.GetWorldPoint(cc.localPoint); + tMat = cc.bodyA.m_xf.R; + tVec = cc.localPoint; + planePointX = cc.bodyA.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + planePointY = cc.bodyA.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + tMat = cc.bodyB.m_xf.R; + for (i = 0; i < cc.pointCount;++i) + { + //clipPoint = cc.bodyB.GetWorldPoint(cc.points[i].localPoint); + tVec = cc.points[i].localPoint; + clipPointX = cc.bodyB.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + clipPointY = cc.bodyB.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + m_separations[i] = (clipPointX - planePointX) * m_normal.x + (clipPointY - planePointY) * m_normal.y - cc.radius; + m_points[i].x = clipPointX; + m_points[i].y = clipPointY; + } + } + break; + case b2Manifold.e_faceB: + { + //m_normal = cc.bodyB.GetWorldVector(cc.localPlaneNormal); + tMat = cc.bodyB.m_xf.R; + tVec = cc.localPlaneNormal; + m_normal.x = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + m_normal.y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //planePoint = cc.bodyB.GetWorldPoint(cc.localPoint); + tMat = cc.bodyB.m_xf.R; + tVec = cc.localPoint; + planePointX = cc.bodyB.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + planePointY = cc.bodyB.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + tMat = cc.bodyA.m_xf.R; + for (i = 0; i < cc.pointCount;++i) + { + //clipPoint = cc.bodyA.GetWorldPoint(cc.points[i].localPoint); + tVec = cc.points[i].localPoint; + clipPointX = cc.bodyA.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + clipPointY = cc.bodyA.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + m_separations[i] = (clipPointX - planePointX) * m_normal.x + (clipPointY - planePointY) * m_normal.y - cc.radius; + m_points[i].Set(clipPointX, clipPointY); + } + + // Ensure normal points from A to B + m_normal.x *= -1; + m_normal.y *= -1; + } + break; + } + } + + public var m_normal:b2Vec2; + public var m_points:Vector.; + public var m_separations:Vector.; +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Controllers/b2BuoyancyController.as b/srclib/Box2D/Dynamics/Controllers/b2BuoyancyController.as new file mode 100644 index 00000000..268fafe9 --- /dev/null +++ b/srclib/Box2D/Dynamics/Controllers/b2BuoyancyController.as @@ -0,0 +1,143 @@ +/* +* Copyright (c) 2006-2007 Adam Newgas +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Controllers{ + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; + + +/** + * Calculates buoyancy forces for fluids in the form of a half plane + */ +public class b2BuoyancyController extends b2Controller +{ + public function b2BuoyancyController() {} + + /** + * The outer surface normal + */ + public var normal:b2Vec2 = new b2Vec2(0,-1); + /** + * The height of the fluid surface along the normal + */ + public var offset:Number = 0; + /** + * The fluid density + */ + public var density:Number = 0; + /** + * Fluid velocity, for drag calculations + */ + public var velocity:b2Vec2 = new b2Vec2(0,0); + /** + * Linear drag co-efficient + */ + public var linearDrag:Number = 2; + /** + * Linear drag co-efficient + */ + public var angularDrag:Number = 1; + /** + * If false, bodies are assumed to be uniformly dense, otherwise use the shapes densities + */ + public var useDensity:Boolean = false; //False by default to prevent a gotcha + /** + * If true, gravity is taken from the world instead of the gravity parameter. + */ + public var useWorldGravity:Boolean = true; + /** + * Gravity vector, if the world's gravity is not used + */ + public var gravity:b2Vec2 = null; + + + public override function Step(step:b2TimeStep):void{ + if(!m_bodyList) + return; + if(useWorldGravity){ + gravity = GetWorld().GetGravity().Copy(); + } + for(var i:b2ControllerEdge=m_bodyList;i;i=i.nextBody){ + var body:b2Body = i.body; + if(body.IsAwake() == false){ + //Buoyancy force is just a function of position, + //so unlike most forces, it is safe to ignore sleeping bodes + continue; + } + var areac:b2Vec2 = new b2Vec2(); + var massc:b2Vec2 = new b2Vec2(); + var area:Number = 0.0; + var mass:Number = 0.0; + for(var fixture:b2Fixture=body.GetFixtureList();fixture;fixture=fixture.GetNext()){ + var sc:b2Vec2 = new b2Vec2(); + var sarea:Number = fixture.GetShape().ComputeSubmergedArea(normal, offset, body.GetTransform(), sc); + area += sarea; + areac.x += sarea * sc.x; + areac.y += sarea * sc.y; + var shapeDensity:Number; + if (useDensity) { + //TODO: Figure out what to do now density is gone + shapeDensity = 1; + }else{ + shapeDensity = 1; + } + mass += sarea*shapeDensity; + massc.x += sarea * sc.x * shapeDensity; + massc.y += sarea * sc.y * shapeDensity; + } + areac.x/=area; + areac.y/=area; + massc.x/=mass; + massc.y/=mass; + if(area= 0); + //b2Settings.b2Assert(m_bodyCount >= 0); + } + + public function Clear():void + { + while (m_bodyList) + RemoveBody(m_bodyList.body); + } + + public function GetNext():b2Controller{return m_next;} + public function GetWorld():b2World { return m_world; } + + public function GetBodyList() : b2ControllerEdge + { + return m_bodyList; + } + + b2internal var m_next:b2Controller; + b2internal var m_prev:b2Controller; + + protected var m_bodyList:b2ControllerEdge; + protected var m_bodyCount:int; + + b2internal var m_world:b2World; +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Controllers/b2ControllerEdge.as b/srclib/Box2D/Dynamics/Controllers/b2ControllerEdge.as new file mode 100644 index 00000000..b41e03e5 --- /dev/null +++ b/srclib/Box2D/Dynamics/Controllers/b2ControllerEdge.as @@ -0,0 +1,22 @@ +package Box2D.Dynamics.Controllers +{ + + import Box2D.Dynamics.b2Body; +public class b2ControllerEdge +{ + public function b2ControllerEdge() {} + + /** provides quick access to other end of this edge */ + public var controller:b2Controller; + /** the body */ + public var body:b2Body; + /** the previous controller edge in the controllers's body list */ + public var prevBody:b2ControllerEdge; + /** the next controller edge in the controllers's body list */ + public var nextBody:b2ControllerEdge; + /** the previous controller edge in the body's controller list */ + public var prevController:b2ControllerEdge; + /** the next controller edge in the body's controller list */ + public var nextController:b2ControllerEdge; +} +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Controllers/b2GravityController.as b/srclib/Box2D/Dynamics/Controllers/b2GravityController.as new file mode 100644 index 00000000..27d0b740 --- /dev/null +++ b/srclib/Box2D/Dynamics/Controllers/b2GravityController.as @@ -0,0 +1,102 @@ +/* +* Copyright (c) 2006-2007 Adam Newgas +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Controllers{ + + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; + + +/** + * Applies simplified gravity between every pair of bodies + */ +public class b2GravityController extends b2Controller +{ + public function b2GravityController() {} + + /** + * Specifies the strength of the gravitiation force + */ + public var G:Number = 1; + /** + * If true, gravity is proportional to r^-2, otherwise r^-1 + */ + public var invSqr:Boolean = true; + + public override function Step(step:b2TimeStep):void{ + //Inlined + var i:b2ControllerEdge = null; + var body1:b2Body = null; + var p1:b2Vec2 = null; + var mass1:Number = 0; + var j:b2ControllerEdge = null; + var body2:b2Body = null; + var p2:b2Vec2 = null; + var dx:Number = 0; + var dy:Number = 0; + var r2:Number = 0; + var f:b2Vec2 = null; + if(invSqr){ + for(i=m_bodyList;i;i=i.nextBody){ + body1 = i.body; + p1 = body1.GetWorldCenter(); + mass1 = body1.GetMass(); + for(j=m_bodyList;j!=i;j=j.nextBody){ + body2 = j.body; + p2 = body2.GetWorldCenter() + dx = p2.x - p1.x; + dy = p2.y - p1.y; + r2 = dx*dx+dy*dy; + if(r20 || yDamping>0){ + maxTimestep = 1/Math.max(xDamping,yDamping); + }else{ + maxTimestep = 0; + } + } + + public override function Step(step:b2TimeStep):void{ + var timestep:Number = step.dt; + if(timestep<=Number.MIN_VALUE) + return; + if(timestep>maxTimestep && maxTimestep>0) + timestep = maxTimestep; + for(var i:b2ControllerEdge=m_bodyList;i;i=i.nextBody){ + var body:b2Body = i.body; + if(!body.IsAwake()){ + //Sleeping bodies are still - so have no damping + continue; + } + var damping:b2Vec2 = + body.GetWorldVector( + b2Math.MulMV(T, + body.GetLocalVector( + body.GetLinearVelocity() + ) + ) + ); + body.SetLinearVelocity(new b2Vec2( + body.GetLinearVelocity().x + damping.x * timestep, + body.GetLinearVelocity().y + damping.y * timestep + )); + } + } +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2DistanceJoint.as b/srclib/Box2D/Dynamics/Joints/b2DistanceJoint.as new file mode 100644 index 00000000..dbb790ca --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2DistanceJoint.as @@ -0,0 +1,357 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints { + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; + +use namespace b2internal; + +// 1-D constrained system +// m (v2 - v1) = lambda +// v2 + (beta/h) * x1 + gamma * lambda = 0, gamma has units of inverse mass. +// x2 = x1 + h * v2 + +// 1-D mass-damper-spring system +// m (v2 - v1) + h * d * v2 + h * k * + +// C = norm(p2 - p1) - L +// u = (p2 - p1) / norm(p2 - p1) +// Cdot = dot(u, v2 + cross(w2, r2) - v1 - cross(w1, r1)) +// J = [-u -cross(r1, u) u cross(r2, u)] +// K = J * invM * JT +// = invMass1 + invI1 * cross(r1, u)^2 + invMass2 + invI2 * cross(r2, u)^2 + +/** +* A distance joint constrains two points on two bodies +* to remain at a fixed distance from each other. You can view +* this as a massless, rigid rod. +* @see b2DistanceJointDef +*/ +public class b2DistanceJoint extends b2Joint +{ + /** @inheritDoc */ + public override function GetAnchorA():b2Vec2{ + return m_bodyA.GetWorldPoint(m_localAnchor1); + } + /** @inheritDoc */ + public override function GetAnchorB():b2Vec2{ + return m_bodyB.GetWorldPoint(m_localAnchor2); + } + + /** @inheritDoc */ + public override function GetReactionForce(inv_dt:Number):b2Vec2 + { + //b2Vec2 F = (m_inv_dt * m_impulse) * m_u; + //return F; + return new b2Vec2(inv_dt * m_impulse * m_u.x, inv_dt * m_impulse * m_u.y); + } + + /** @inheritDoc */ + public override function GetReactionTorque(inv_dt:Number):Number + { + //B2_NOT_USED(inv_dt); + return 0.0; + } + + /// Set the natural length + public function GetLength():Number + { + return m_length; + } + + /// Get the natural length + public function SetLength(length:Number):void + { + m_length = length; + } + + /// Get the frequency in Hz + public function GetFrequency():Number + { + return m_frequencyHz; + } + + /// Set the frequency in Hz + public function SetFrequency(hz:Number):void + { + m_frequencyHz = hz; + } + + /// Get damping ratio + public function GetDampingRatio():Number + { + return m_dampingRatio; + } + + /// Set damping ratio + public function SetDampingRatio(ratio:Number):void + { + m_dampingRatio = ratio; + } + + //--------------- Internals Below ------------------- + + /** @private */ + public function b2DistanceJoint(def:b2DistanceJointDef){ + super(def); + + var tMat:b2Mat22; + var tX:Number; + var tY:Number; + m_localAnchor1.SetV(def.localAnchorA); + m_localAnchor2.SetV(def.localAnchorB); + + m_length = def.length; + m_frequencyHz = def.frequencyHz; + m_dampingRatio = def.dampingRatio; + m_impulse = 0.0; + m_gamma = 0.0; + m_bias = 0.0; + } + + b2internal override function InitVelocityConstraints(step:b2TimeStep) : void{ + + var tMat:b2Mat22; + var tX:Number; + + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + // Compute the effective mass matrix. + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - bA.m_sweep.localCenter.x; + var r1Y:Number = m_localAnchor1.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - bB.m_sweep.localCenter.x; + var r2Y:Number = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + //m_u = bB->m_sweep.c + r2 - bA->m_sweep.c - r1; + m_u.x = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X; + m_u.y = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y; + + // Handle singularity. + //float32 length = m_u.Length(); + var length:Number = Math.sqrt(m_u.x*m_u.x + m_u.y*m_u.y); + if (length > b2Settings.b2_linearSlop) + { + //m_u *= 1.0 / length; + m_u.Multiply( 1.0 / length ); + } + else + { + m_u.SetZero(); + } + + //float32 cr1u = b2Cross(r1, m_u); + var cr1u:Number = (r1X * m_u.y - r1Y * m_u.x); + //float32 cr2u = b2Cross(r2, m_u); + var cr2u:Number = (r2X * m_u.y - r2Y * m_u.x); + //m_mass = bA->m_invMass + bA->m_invI * cr1u * cr1u + bB->m_invMass + bB->m_invI * cr2u * cr2u; + var invMass:Number = bA.m_invMass + bA.m_invI * cr1u * cr1u + bB.m_invMass + bB.m_invI * cr2u * cr2u; + m_mass = invMass != 0.0 ? 1.0 / invMass : 0.0; + + if (m_frequencyHz > 0.0) + { + var C:Number = length - m_length; + + // Frequency + var omega:Number = 2.0 * Math.PI * m_frequencyHz; + + // Damping coefficient + var d:Number = 2.0 * m_mass * m_dampingRatio * omega; + + // Spring stiffness + var k:Number = m_mass * omega * omega; + + // magic formulas + m_gamma = step.dt * (d + step.dt * k); + m_gamma = m_gamma != 0.0?1 / m_gamma:0.0; + m_bias = C * step.dt * k * m_gamma; + + m_mass = invMass + m_gamma; + m_mass = m_mass != 0.0 ? 1.0 / m_mass : 0.0; + } + + if (step.warmStarting) + { + // Scale the impulse to support a variable time step + m_impulse *= step.dtRatio; + + //b2Vec2 P = m_impulse * m_u; + var PX:Number = m_impulse * m_u.x; + var PY:Number = m_impulse * m_u.y; + //bA->m_linearVelocity -= bA->m_invMass * P; + bA.m_linearVelocity.x -= bA.m_invMass * PX; + bA.m_linearVelocity.y -= bA.m_invMass * PY; + //bA->m_angularVelocity -= bA->m_invI * b2Cross(r1, P); + bA.m_angularVelocity -= bA.m_invI * (r1X * PY - r1Y * PX); + //bB->m_linearVelocity += bB->m_invMass * P; + bB.m_linearVelocity.x += bB.m_invMass * PX; + bB.m_linearVelocity.y += bB.m_invMass * PY; + //bB->m_angularVelocity += bB->m_invI * b2Cross(r2, P); + bB.m_angularVelocity += bB.m_invI * (r2X * PY - r2Y * PX); + } + else + { + m_impulse = 0.0; + } + } + + + + b2internal override function SolveVelocityConstraints(step:b2TimeStep): void{ + + var tMat:b2Mat22; + + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - bA.m_sweep.localCenter.x; + var r1Y:Number = m_localAnchor1.y - bA.m_sweep.localCenter.y; + var tX:Number = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - bB.m_sweep.localCenter.x; + var r2Y:Number = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + // Cdot = dot(u, v + cross(w, r)) + //b2Vec2 v1 = bA->m_linearVelocity + b2Cross(bA->m_angularVelocity, r1); + var v1X:Number = bA.m_linearVelocity.x + (-bA.m_angularVelocity * r1Y); + var v1Y:Number = bA.m_linearVelocity.y + (bA.m_angularVelocity * r1X); + //b2Vec2 v2 = bB->m_linearVelocity + b2Cross(bB->m_angularVelocity, r2); + var v2X:Number = bB.m_linearVelocity.x + (-bB.m_angularVelocity * r2Y); + var v2Y:Number = bB.m_linearVelocity.y + (bB.m_angularVelocity * r2X); + //float32 Cdot = b2Dot(m_u, v2 - v1); + var Cdot:Number = (m_u.x * (v2X - v1X) + m_u.y * (v2Y - v1Y)); + + var impulse:Number = -m_mass * (Cdot + m_bias + m_gamma * m_impulse); + m_impulse += impulse; + + //b2Vec2 P = impulse * m_u; + var PX:Number = impulse * m_u.x; + var PY:Number = impulse * m_u.y; + //bA->m_linearVelocity -= bA->m_invMass * P; + bA.m_linearVelocity.x -= bA.m_invMass * PX; + bA.m_linearVelocity.y -= bA.m_invMass * PY; + //bA->m_angularVelocity -= bA->m_invI * b2Cross(r1, P); + bA.m_angularVelocity -= bA.m_invI * (r1X * PY - r1Y * PX); + //bB->m_linearVelocity += bB->m_invMass * P; + bB.m_linearVelocity.x += bB.m_invMass * PX; + bB.m_linearVelocity.y += bB.m_invMass * PY; + //bB->m_angularVelocity += bB->m_invI * b2Cross(r2, P); + bB.m_angularVelocity += bB.m_invI * (r2X * PY - r2Y * PX); + } + + b2internal override function SolvePositionConstraints(baumgarte:Number):Boolean + { + //B2_NOT_USED(baumgarte); + + var tMat:b2Mat22; + + if (m_frequencyHz > 0.0) + { + // There is no position correction for soft distance constraints + return true; + } + + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - bA.m_sweep.localCenter.x; + var r1Y:Number = m_localAnchor1.y - bA.m_sweep.localCenter.y; + var tX:Number = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - bB.m_sweep.localCenter.x; + var r2Y:Number = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + //b2Vec2 d = bB->m_sweep.c + r2 - bA->m_sweep.c - r1; + var dX:Number = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X; + var dY:Number = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y; + + //float32 length = d.Normalize(); + var length:Number = Math.sqrt(dX*dX + dY*dY); + dX /= length; + dY /= length; + //float32 C = length - m_length; + var C:Number = length - m_length; + C = b2Math.Clamp(C, -b2Settings.b2_maxLinearCorrection, b2Settings.b2_maxLinearCorrection); + + var impulse:Number = -m_mass * C; + //m_u = d; + m_u.Set(dX, dY); + //b2Vec2 P = impulse * m_u; + var PX:Number = impulse * m_u.x; + var PY:Number = impulse * m_u.y; + + //bA->m_sweep.c -= bA->m_invMass * P; + bA.m_sweep.c.x -= bA.m_invMass * PX; + bA.m_sweep.c.y -= bA.m_invMass * PY; + //bA->m_sweep.a -= bA->m_invI * b2Cross(r1, P); + bA.m_sweep.a -= bA.m_invI * (r1X * PY - r1Y * PX); + //bB->m_sweep.c += bB->m_invMass * P; + bB.m_sweep.c.x += bB.m_invMass * PX; + bB.m_sweep.c.y += bB.m_invMass * PY; + //bB->m_sweep.a -= bB->m_invI * b2Cross(r2, P); + bB.m_sweep.a += bB.m_invI * (r2X * PY - r2Y * PX); + + bA.SynchronizeTransform(); + bB.SynchronizeTransform(); + + return b2Math.Abs(C) < b2Settings.b2_linearSlop; + + } + + private var m_localAnchor1:b2Vec2 = new b2Vec2(); + private var m_localAnchor2:b2Vec2 = new b2Vec2(); + private var m_u:b2Vec2 = new b2Vec2(); + private var m_frequencyHz:Number; + private var m_dampingRatio:Number; + private var m_gamma:Number; + private var m_bias:Number; + private var m_impulse:Number; + private var m_mass:Number; // effective mass for the constraint. + private var m_length:Number; +}; + +} diff --git a/srclib/Box2D/Dynamics/Joints/b2DistanceJointDef.as b/srclib/Box2D/Dynamics/Joints/b2DistanceJointDef.as new file mode 100644 index 00000000..183b3137 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2DistanceJointDef.as @@ -0,0 +1,93 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + import Box2D.Dynamics.*; + +use namespace b2internal; + + +/** +* Distance joint definition. This requires defining an +* anchor point on both bodies and the non-zero length of the +* distance joint. The definition uses local anchor points +* so that the initial configuration can violate the constraint +* slightly. This helps when saving and loading a game. +* @warning Do not use a zero or short length. +* @see b2DistanceJoint +*/ +public class b2DistanceJointDef extends b2JointDef +{ + public function b2DistanceJointDef() + { + type = b2Joint.e_distanceJoint; + //localAnchor1.Set(0.0, 0.0); + //localAnchor2.Set(0.0, 0.0); + length = 1.0; + frequencyHz = 0.0; + dampingRatio = 0.0; + } + + /** + * Initialize the bodies, anchors, and length using the world + * anchors. + */ + public function Initialize(bA:b2Body, bB:b2Body, + anchorA:b2Vec2, anchorB:b2Vec2) : void + { + bodyA = bA; + bodyB = bB; + localAnchorA.SetV( bodyA.GetLocalPoint(anchorA)); + localAnchorB.SetV( bodyB.GetLocalPoint(anchorB)); + var dX:Number = anchorB.x - anchorA.x; + var dY:Number = anchorB.y - anchorA.y; + length = Math.sqrt(dX*dX + dY*dY); + frequencyHz = 0.0; + dampingRatio = 0.0; + } + + /** + * The local anchor point relative to body1's origin. + */ + public var localAnchorA:b2Vec2 = new b2Vec2(); + + /** + * The local anchor point relative to body2's origin. + */ + public var localAnchorB:b2Vec2 = new b2Vec2(); + + /** + * The natural length between the anchor points. + */ + public var length:Number; + + /** + * The mass-spring-damper frequency in Hertz. + */ + public var frequencyHz:Number; + + /** + * The damping ratio. 0 = no damping, 1 = critical damping. + */ + public var dampingRatio:Number; +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2FrictionJoint.as b/srclib/Box2D/Dynamics/Joints/b2FrictionJoint.as new file mode 100644 index 00000000..4b8a0139 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2FrictionJoint.as @@ -0,0 +1,297 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints { + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; + +use namespace b2internal; + +// Point-to-point constraint +// Cdot = v2 - v1 +// = v2 + cross(w2, r2) - v1 - cross(w1, r1) +// J = [-I -r1_skew I r2_skew ] +// Identity used: +// w k % (rx i + ry j) = w * (-ry i + rx j) + +// Angle constraint +// Cdot = w2 - w1 +// J = [0 0 -1 0 0 1] +// K = invI1 + invI2 + +/** + * Friction joint. This is used for top-down friction. + * It provides 2D translational friction and angular friction. + * @see b2FrictionJointDef + */ +public class b2FrictionJoint extends b2Joint +{ + /** @inheritDoc */ + public override function GetAnchorA():b2Vec2{ + return m_bodyA.GetWorldPoint(m_localAnchorA); + } + /** @inheritDoc */ + public override function GetAnchorB():b2Vec2{ + return m_bodyB.GetWorldPoint(m_localAnchorB); + } + + /** @inheritDoc */ + public override function GetReactionForce(inv_dt:Number):b2Vec2 + { + return new b2Vec2(inv_dt * m_linearImpulse.x, inv_dt * m_linearImpulse.y); + } + + /** @inheritDoc */ + public override function GetReactionTorque(inv_dt:Number):Number + { + //B2_NOT_USED(inv_dt); + return inv_dt * m_angularImpulse; + } + + public function SetMaxForce(force:Number):void + { + m_maxForce = force; + } + + public function GetMaxForce():Number + { + return m_maxForce; + } + + public function SetMaxTorque(torque:Number):void + { + m_maxTorque = torque; + } + + public function GetMaxTorque():Number + { + return m_maxTorque; + } + + //--------------- Internals Below ------------------- + + /** @private */ + public function b2FrictionJoint(def:b2FrictionJointDef){ + super(def); + + m_localAnchorA.SetV(def.localAnchorA); + m_localAnchorB.SetV(def.localAnchorB); + + m_linearMass.SetZero(); + m_angularMass = 0.0; + + m_linearImpulse.SetZero(); + m_angularImpulse = 0.0; + + m_maxForce = def.maxForce; + m_maxTorque = def.maxTorque; + } + + b2internal override function InitVelocityConstraints(step:b2TimeStep) : void { + var tMat:b2Mat22; + var tX:Number; + + var bA:b2Body = m_bodyA; + var bB:b2Body= m_bodyB; + + // Compute the effective mass matrix. + //b2Vec2 rA = b2Mul(bA->m_xf.R, m_localAnchorA - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var rAX:Number = m_localAnchorA.x - bA.m_sweep.localCenter.x; + var rAY:Number = m_localAnchorA.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * rAX + tMat.col2.x * rAY); + rAY = (tMat.col1.y * rAX + tMat.col2.y * rAY); + rAX = tX; + //b2Vec2 rB = b2Mul(bB->m_xf.R, m_localAnchorB - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var rBX:Number = m_localAnchorB.x - bB.m_sweep.localCenter.x; + var rBY:Number = m_localAnchorB.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * rBX + tMat.col2.x * rBY); + rBY = (tMat.col1.y * rBX + tMat.col2.y * rBY); + rBX = tX; + + // J = [-I -r1_skew I r2_skew] + // [ 0 -1 0 1] + // r_skew = [-ry; rx] + + // Matlab + // K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x, -r1y*iA-r2y*iB] + // [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB, r1x*iA+r2x*iB] + // [ -r1y*iA-r2y*iB, r1x*iA+r2x*iB, iA+iB] + + var mA:Number = bA.m_invMass + var mB:Number = bB.m_invMass; + var iA:Number = bA.m_invI + var iB:Number = bB.m_invI; + + var K:b2Mat22 = new b2Mat22(); + K.col1.x = mA + mB; K.col2.x = 0.0; + K.col1.y = 0.0; K.col2.y = mA + mB; + + K.col1.x+= iA * rAY * rAY; K.col2.x+= -iA * rAX * rAY; + K.col1.y+= -iA * rAX * rAY; K.col2.y+= iA * rAX * rAX; + + K.col1.x+= iB * rBY * rBY; K.col2.x+= -iB * rBX * rBY; + K.col1.y+= -iB * rBX * rBY; K.col2.y+= iB * rBX * rBX; + + K.GetInverse(m_linearMass); + + m_angularMass = iA + iB; + if (m_angularMass > 0.0) + { + m_angularMass = 1.0 / m_angularMass; + } + + if (step.warmStarting) + { + // Scale impulses to support a variable time step. + m_linearImpulse.x *= step.dtRatio; + m_linearImpulse.y *= step.dtRatio; + m_angularImpulse *= step.dtRatio; + + var P:b2Vec2 = m_linearImpulse; + + bA.m_linearVelocity.x -= mA * P.x; + bA.m_linearVelocity.y -= mA * P.y; + bA.m_angularVelocity -= iA * (rAX * P.y - rAY * P.x + m_angularImpulse); + + bB.m_linearVelocity.x += mB * P.x; + bB.m_linearVelocity.y += mB * P.y; + bB.m_angularVelocity += iB * (rBX * P.y - rBY * P.x + m_angularImpulse); + } + else + { + m_linearImpulse.SetZero(); + m_angularImpulse = 0.0; + } + + } + + + + b2internal override function SolveVelocityConstraints(step:b2TimeStep): void{ + //B2_NOT_USED(step); + var tMat:b2Mat22; + var tX:Number; + + var bA:b2Body = m_bodyA; + var bB:b2Body= m_bodyB; + + var vA:b2Vec2 = bA.m_linearVelocity; + var wA:Number = bA.m_angularVelocity; + var vB:b2Vec2 = bB.m_linearVelocity; + var wB:Number = bB.m_angularVelocity; + + var mA:Number = bA.m_invMass + var mB:Number = bB.m_invMass; + var iA:Number = bA.m_invI + var iB:Number = bB.m_invI; + + //b2Vec2 rA = b2Mul(bA->m_xf.R, m_localAnchorA - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var rAX:Number = m_localAnchorA.x - bA.m_sweep.localCenter.x; + var rAY:Number = m_localAnchorA.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * rAX + tMat.col2.x * rAY); + rAY = (tMat.col1.y * rAX + tMat.col2.y * rAY); + rAX = tX; + //b2Vec2 rB = b2Mul(bB->m_xf.R, m_localAnchorB - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var rBX:Number = m_localAnchorB.x - bB.m_sweep.localCenter.x; + var rBY:Number = m_localAnchorB.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * rBX + tMat.col2.x * rBY); + rBY = (tMat.col1.y * rBX + tMat.col2.y * rBY); + rBX = tX; + + var maxImpulse:Number; + + // Solve angular friction + { + var Cdot:Number = wB - wA; + var impulse:Number = -m_angularMass * Cdot; + + var oldImpulse:Number = m_angularImpulse; + maxImpulse = step.dt * m_maxTorque; + m_angularImpulse = b2Math.Clamp(m_angularImpulse + impulse, -maxImpulse, maxImpulse); + impulse = m_angularImpulse - oldImpulse; + + wA -= iA * impulse; + wB += iB * impulse; + } + + // Solve linear friction + { + //b2Vec2 Cdot = vB + b2Cross(wB, rB) - vA - b2Cross(wA, rA); + var CdotX:Number = vB.x - wB * rBY - vA.x + wA * rAY; + var CdotY:Number = vB.y + wB * rBX - vA.y - wA * rAX; + + var impulseV:b2Vec2 = b2Math.MulMV(m_linearMass, new b2Vec2(-CdotX, -CdotY)); + var oldImpulseV:b2Vec2 = m_linearImpulse.Copy(); + + m_linearImpulse.Add(impulseV); + + maxImpulse = step.dt * m_maxForce; + + if (m_linearImpulse.LengthSquared() > maxImpulse * maxImpulse) + { + m_linearImpulse.Normalize(); + m_linearImpulse.Multiply(maxImpulse); + } + + impulseV = b2Math.SubtractVV(m_linearImpulse, oldImpulseV); + + vA.x -= mA * impulseV.x; + vA.y -= mA * impulseV.y; + wA -= iA * (rAX * impulseV.y - rAY * impulseV.x); + + vB.x += mB * impulseV.x; + vB.y += mB * impulseV.y; + wB += iB * (rBX * impulseV.y - rBY * impulseV.x); + } + + // References has made some sets unnecessary + //bA->m_linearVelocity = vA; + bA.m_angularVelocity = wA; + //bB->m_linearVelocity = vB; + bB.m_angularVelocity = wB; + + } + + b2internal override function SolvePositionConstraints(baumgarte:Number):Boolean + { + //B2_NOT_USED(baumgarte); + + return true; + + } + + private var m_localAnchorA:b2Vec2 = new b2Vec2(); + private var m_localAnchorB:b2Vec2 = new b2Vec2(); + + public var m_linearMass:b2Mat22 = new b2Mat22(); + public var m_angularMass:Number; + + private var m_linearImpulse:b2Vec2 = new b2Vec2(); + private var m_angularImpulse:Number; + + private var m_maxForce:Number; + private var m_maxTorque:Number; +}; + +} diff --git a/srclib/Box2D/Dynamics/Joints/b2FrictionJointDef.as b/srclib/Box2D/Dynamics/Joints/b2FrictionJointDef.as new file mode 100644 index 00000000..0fbea808 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2FrictionJointDef.as @@ -0,0 +1,75 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + import Box2D.Dynamics.*; + +use namespace b2internal; + + +/** + * Friction joint defintion + * @see b2FrictionJoint + */ +public class b2FrictionJointDef extends b2JointDef +{ + public function b2FrictionJointDef() + { + type = b2Joint.e_frictionJoint; + maxForce = 0.0; + maxTorque = 0.0; + } + + /** + * Initialize the bodies, anchors, axis, and reference angle using the world + * anchor and world axis. + */ + public function Initialize(bA:b2Body, bB:b2Body, + anchor:b2Vec2) : void + { + bodyA = bA; + bodyB = bB; + localAnchorA.SetV( bodyA.GetLocalPoint(anchor)); + localAnchorB.SetV( bodyB.GetLocalPoint(anchor)); + } + + /** + * The local anchor point relative to bodyA's origin. + */ + public var localAnchorA:b2Vec2 = new b2Vec2(); + + /** + * The local anchor point relative to bodyB's origin. + */ + public var localAnchorB:b2Vec2 = new b2Vec2(); + + /** + * The maximun force in N. + */ + public var maxForce:Number; + + /** + * The maximun friction torque in N-m + */ + public var maxTorque:Number; +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2GearJoint.as b/srclib/Box2D/Dynamics/Joints/b2GearJoint.as new file mode 100644 index 00000000..f7a296c9 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2GearJoint.as @@ -0,0 +1,356 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints { + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; + + + +use namespace b2internal; + + + + +/** +* A gear joint is used to connect two joints together. Either joint +* can be a revolute or prismatic joint. You specify a gear ratio +* to bind the motions together: +* coordinate1 + ratio * coordinate2 = constant +* The ratio can be negative or positive. If one joint is a revolute joint +* and the other joint is a prismatic joint, then the ratio will have units +* of length or units of 1/length. +* @warning The revolute and prismatic joints must be attached to +* fixed bodies (which must be body1 on those joints). +* @see b2GearJointDef +*/ + +public class b2GearJoint extends b2Joint +{ + /** @inheritDoc */ + public override function GetAnchorA():b2Vec2{ + //return m_bodyA->GetWorldPoint(m_localAnchor1); + return m_bodyA.GetWorldPoint(m_localAnchor1); + } + /** @inheritDoc */ + public override function GetAnchorB():b2Vec2{ + //return m_bodyB->GetWorldPoint(m_localAnchor2); + return m_bodyB.GetWorldPoint(m_localAnchor2); + } + /** @inheritDoc */ + public override function GetReactionForce(inv_dt:Number):b2Vec2{ + // TODO_ERIN not tested + // b2Vec2 P = m_impulse * m_J.linear2; + //return inv_dt * P; + return new b2Vec2(inv_dt * m_impulse * m_J.linearB.x, inv_dt * m_impulse * m_J.linearB.y); + } + /** @inheritDoc */ + public override function GetReactionTorque(inv_dt:Number):Number{ + // TODO_ERIN not tested + //b2Vec2 r = b2Mul(m_bodyB->m_xf.R, m_localAnchor2 - m_bodyB->GetLocalCenter()); + var tMat:b2Mat22 = m_bodyB.m_xf.R; + var rX:Number = m_localAnchor1.x - m_bodyB.m_sweep.localCenter.x; + var rY:Number = m_localAnchor1.y - m_bodyB.m_sweep.localCenter.y; + var tX:Number = tMat.col1.x * rX + tMat.col2.x * rY; + rY = tMat.col1.y * rX + tMat.col2.y * rY; + rX = tX; + //b2Vec2 P = m_impulse * m_J.linearB; + var PX:Number = m_impulse * m_J.linearB.x; + var PY:Number = m_impulse * m_J.linearB.y; + //float32 L = m_impulse * m_J.angularB - b2Cross(r, P); + //return inv_dt * L; + return inv_dt * (m_impulse * m_J.angularB - rX * PY + rY * PX); + } + + /** + * Get the gear ratio. + */ + public function GetRatio():Number{ + return m_ratio; + } + + /** + * Set the gear ratio. + */ + public function SetRatio(ratio:Number):void { + //b2Settings.b2Assert(b2Math.b2IsValid(ratio)); + m_ratio = ratio; + } + + //--------------- Internals Below ------------------- + + /** @private */ + public function b2GearJoint(def:b2GearJointDef){ + // parent constructor + super(def); + + var type1:int = def.joint1.m_type; + var type2:int = def.joint2.m_type; + + //b2Settings.b2Assert(type1 == b2Joint.e_revoluteJoint || type1 == b2Joint.e_prismaticJoint); + //b2Settings.b2Assert(type2 == b2Joint.e_revoluteJoint || type2 == b2Joint.e_prismaticJoint); + //b2Settings.b2Assert(def.joint1.GetBodyA().GetType() == b2Body.b2_staticBody); + //b2Settings.b2Assert(def.joint2.GetBodyA().GetType() == b2Body.b2_staticBody); + + m_revolute1 = null; + m_prismatic1 = null; + m_revolute2 = null; + m_prismatic2 = null; + + var coordinate1:Number; + var coordinate2:Number; + + m_ground1 = def.joint1.GetBodyA(); + m_bodyA = def.joint1.GetBodyB(); + if (type1 == b2Joint.e_revoluteJoint) + { + m_revolute1 = def.joint1 as b2RevoluteJoint; + m_groundAnchor1.SetV( m_revolute1.m_localAnchor1 ); + m_localAnchor1.SetV( m_revolute1.m_localAnchor2 ); + coordinate1 = m_revolute1.GetJointAngle(); + } + else + { + m_prismatic1 = def.joint1 as b2PrismaticJoint; + m_groundAnchor1.SetV( m_prismatic1.m_localAnchor1 ); + m_localAnchor1.SetV( m_prismatic1.m_localAnchor2 ); + coordinate1 = m_prismatic1.GetJointTranslation(); + } + + m_ground2 = def.joint2.GetBodyA(); + m_bodyB = def.joint2.GetBodyB(); + if (type2 == b2Joint.e_revoluteJoint) + { + m_revolute2 = def.joint2 as b2RevoluteJoint; + m_groundAnchor2.SetV( m_revolute2.m_localAnchor1 ); + m_localAnchor2.SetV( m_revolute2.m_localAnchor2 ); + coordinate2 = m_revolute2.GetJointAngle(); + } + else + { + m_prismatic2 = def.joint2 as b2PrismaticJoint; + m_groundAnchor2.SetV( m_prismatic2.m_localAnchor1 ); + m_localAnchor2.SetV( m_prismatic2.m_localAnchor2 ); + coordinate2 = m_prismatic2.GetJointTranslation(); + } + + m_ratio = def.ratio; + + m_constant = coordinate1 + m_ratio * coordinate2; + + m_impulse = 0.0; + + } + + b2internal override function InitVelocityConstraints(step:b2TimeStep) : void{ + var g1:b2Body = m_ground1; + var g2:b2Body = m_ground2; + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + // temp vars + var ugX:Number; + var ugY:Number; + var rX:Number; + var rY:Number; + var tMat:b2Mat22; + var tVec:b2Vec2; + var crug:Number; + var tX:Number; + + var K:Number = 0.0; + m_J.SetZero(); + + if (m_revolute1) + { + m_J.angularA = -1.0; + K += bA.m_invI; + } + else + { + //b2Vec2 ug = b2MulMV(g1->m_xf.R, m_prismatic1->m_localXAxis1); + tMat = g1.m_xf.R; + tVec = m_prismatic1.m_localXAxis1; + ugX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + ugY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //b2Vec2 r = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + rX = m_localAnchor1.x - bA.m_sweep.localCenter.x; + rY = m_localAnchor1.y - bA.m_sweep.localCenter.y; + tX = tMat.col1.x * rX + tMat.col2.x * rY; + rY = tMat.col1.y * rX + tMat.col2.y * rY; + rX = tX; + + //var crug:Number = b2Cross(r, ug); + crug = rX * ugY - rY * ugX; + //m_J.linearA = -ug; + m_J.linearA.Set(-ugX, -ugY); + m_J.angularA = -crug; + K += bA.m_invMass + bA.m_invI * crug * crug; + } + + if (m_revolute2) + { + m_J.angularB = -m_ratio; + K += m_ratio * m_ratio * bB.m_invI; + } + else + { + //b2Vec2 ug = b2Mul(g2->m_xf.R, m_prismatic2->m_localXAxis1); + tMat = g2.m_xf.R; + tVec = m_prismatic2.m_localXAxis1; + ugX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y; + ugY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y; + //b2Vec2 r = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + rX = m_localAnchor2.x - bB.m_sweep.localCenter.x; + rY = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = tMat.col1.x * rX + tMat.col2.x * rY; + rY = tMat.col1.y * rX + tMat.col2.y * rY; + rX = tX; + + //float32 crug = b2Cross(r, ug); + crug = rX * ugY - rY * ugX; + //m_J.linearB = -m_ratio * ug; + m_J.linearB.Set(-m_ratio*ugX, -m_ratio*ugY); + m_J.angularB = -m_ratio * crug; + K += m_ratio * m_ratio * (bB.m_invMass + bB.m_invI * crug * crug); + } + + // Compute effective mass. + m_mass = K > 0.0?1.0 / K:0.0; + + if (step.warmStarting) + { + // Warm starting. + //bA.m_linearVelocity += bA.m_invMass * m_impulse * m_J.linearA; + bA.m_linearVelocity.x += bA.m_invMass * m_impulse * m_J.linearA.x; + bA.m_linearVelocity.y += bA.m_invMass * m_impulse * m_J.linearA.y; + bA.m_angularVelocity += bA.m_invI * m_impulse * m_J.angularA; + //bB.m_linearVelocity += bB.m_invMass * m_impulse * m_J.linearB; + bB.m_linearVelocity.x += bB.m_invMass * m_impulse * m_J.linearB.x; + bB.m_linearVelocity.y += bB.m_invMass * m_impulse * m_J.linearB.y; + bB.m_angularVelocity += bB.m_invI * m_impulse * m_J.angularB; + } + else + { + m_impulse = 0.0; + } + } + + b2internal override function SolveVelocityConstraints(step:b2TimeStep): void + { + //B2_NOT_USED(step); + + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var Cdot:Number = m_J.Compute( bA.m_linearVelocity, bA.m_angularVelocity, + bB.m_linearVelocity, bB.m_angularVelocity); + + var impulse:Number = - m_mass * Cdot; + m_impulse += impulse; + + bA.m_linearVelocity.x += bA.m_invMass * impulse * m_J.linearA.x; + bA.m_linearVelocity.y += bA.m_invMass * impulse * m_J.linearA.y; + bA.m_angularVelocity += bA.m_invI * impulse * m_J.angularA; + bB.m_linearVelocity.x += bB.m_invMass * impulse * m_J.linearB.x; + bB.m_linearVelocity.y += bB.m_invMass * impulse * m_J.linearB.y; + bB.m_angularVelocity += bB.m_invI * impulse * m_J.angularB; + } + + b2internal override function SolvePositionConstraints(baumgarte:Number):Boolean + { + //B2_NOT_USED(baumgarte); + + var linearError:Number = 0.0; + + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var coordinate1:Number; + var coordinate2:Number; + if (m_revolute1) + { + coordinate1 = m_revolute1.GetJointAngle(); + } + else + { + coordinate1 = m_prismatic1.GetJointTranslation(); + } + + if (m_revolute2) + { + coordinate2 = m_revolute2.GetJointAngle(); + } + else + { + coordinate2 = m_prismatic2.GetJointTranslation(); + } + + var C:Number = m_constant - (coordinate1 + m_ratio * coordinate2); + + var impulse:Number = -m_mass * C; + + bA.m_sweep.c.x += bA.m_invMass * impulse * m_J.linearA.x; + bA.m_sweep.c.y += bA.m_invMass * impulse * m_J.linearA.y; + bA.m_sweep.a += bA.m_invI * impulse * m_J.angularA; + bB.m_sweep.c.x += bB.m_invMass * impulse * m_J.linearB.x; + bB.m_sweep.c.y += bB.m_invMass * impulse * m_J.linearB.y; + bB.m_sweep.a += bB.m_invI * impulse * m_J.angularB; + + bA.SynchronizeTransform(); + bB.SynchronizeTransform(); + + // TODO_ERIN not implemented + return linearError < b2Settings.b2_linearSlop; + } + + private var m_ground1:b2Body; + private var m_ground2:b2Body; + + // One of these is NULL. + private var m_revolute1:b2RevoluteJoint; + private var m_prismatic1:b2PrismaticJoint; + + // One of these is NULL. + private var m_revolute2:b2RevoluteJoint; + private var m_prismatic2:b2PrismaticJoint; + + private var m_groundAnchor1:b2Vec2 = new b2Vec2(); + private var m_groundAnchor2:b2Vec2 = new b2Vec2(); + + private var m_localAnchor1:b2Vec2 = new b2Vec2(); + private var m_localAnchor2:b2Vec2 = new b2Vec2(); + + private var m_J:b2Jacobian = new b2Jacobian(); + + private var m_constant:Number; + private var m_ratio:Number; + + // Effective mass + private var m_mass:Number; + + // Impulse for accumulation/warm starting. + private var m_impulse:Number; +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2GearJointDef.as b/srclib/Box2D/Dynamics/Joints/b2GearJointDef.as new file mode 100644 index 00000000..747392ba --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2GearJointDef.as @@ -0,0 +1,60 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints { + + import Box2D.Common.b2internal; + + +use namespace b2internal; + + + +/** +* Gear joint definition. This definition requires two existing +* revolute or prismatic joints (any combination will work). +* The provided joints must attach a dynamic body to a static body. +* @see b2GearJoint +*/ + +public class b2GearJointDef extends b2JointDef +{ + public function b2GearJointDef() + { + type = b2Joint.e_gearJoint; + joint1 = null; + joint2 = null; + ratio = 1.0; + } + + /** + * The first revolute/prismatic joint attached to the gear joint. + */ + public var joint1:b2Joint; + /** + * The second revolute/prismatic joint attached to the gear joint. + */ + public var joint2:b2Joint; + /** + * The gear ratio. + * @see b2GearJoint for explanation. + */ + public var ratio:Number; +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2Jacobian.as b/srclib/Box2D/Dynamics/Joints/b2Jacobian.as new file mode 100644 index 00000000..2d0b9d75 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2Jacobian.as @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; +use namespace b2internal; + + +/** +* @private +*/ +public class b2Jacobian +{ + public function b2Jacobian() {} + + public var linearA:b2Vec2 = new b2Vec2(); + public var angularA:Number; + public var linearB:b2Vec2 = new b2Vec2(); + public var angularB:Number; + + public function SetZero() : void{ + linearA.SetZero(); angularA = 0.0; + linearB.SetZero(); angularB = 0.0; + } + public function Set(x1:b2Vec2, a1:Number, x2:b2Vec2, a2:Number) : void{ + linearA.SetV(x1); angularA = a1; + linearB.SetV(x2); angularB = a2; + } + public function Compute(x1:b2Vec2, a1:Number, x2:b2Vec2, a2:Number):Number{ + + //return b2Math.b2Dot(linearA, x1) + angularA * a1 + b2Math.b2Dot(linearV, x2) + angularV * a2; + return (linearA.x*x1.x + linearA.y*x1.y) + angularA * a1 + (linearB.x*x2.x + linearB.y*x2.y) + angularB * a2; + } +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2Joint.as b/srclib/Box2D/Dynamics/Joints/b2Joint.as new file mode 100644 index 00000000..828350dc --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2Joint.as @@ -0,0 +1,295 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; +use namespace b2internal; + + +/** +* The base joint class. Joints are used to constraint two bodies together in +* various fashions. Some joints also feature limits and motors. +* @see b2JointDef +*/ +public class b2Joint +{ + /** + * Get the type of the concrete joint. + */ + public function GetType():int{ + return m_type; + } + + /** + * Get the anchor point on bodyA in world coordinates. + */ + public virtual function GetAnchorA():b2Vec2{return null}; + /** + * Get the anchor point on bodyB in world coordinates. + */ + public virtual function GetAnchorB():b2Vec2{return null}; + + /** + * Get the reaction force on body2 at the joint anchor in Newtons. + */ + public virtual function GetReactionForce(inv_dt:Number):b2Vec2 {return null}; + /** + * Get the reaction torque on body2 in N*m. + */ + public virtual function GetReactionTorque(inv_dt:Number):Number {return 0.0} + + /** + * Get the first body attached to this joint. + */ + public function GetBodyA():b2Body + { + return m_bodyA; + } + + /** + * Get the second body attached to this joint. + */ + public function GetBodyB():b2Body + { + return m_bodyB; + } + + /** + * Get the next joint the world joint list. + */ + public function GetNext():b2Joint{ + return m_next; + } + + /** + * Get the user data pointer. + */ + public function GetUserData():*{ + return m_userData; + } + + /** + * Set the user data pointer. + */ + public function SetUserData(data:*):void{ + m_userData = data; + } + + /** + * Short-cut function to determine if either body is inactive. + * @return + */ + public function IsActive():Boolean { + return m_bodyA.IsActive() && m_bodyB.IsActive(); + } + + //--------------- Internals Below ------------------- + + static b2internal function Create(def:b2JointDef, allocator:*):b2Joint{ + var joint:b2Joint = null; + + switch (def.type) + { + case e_distanceJoint: + { + //void* mem = allocator->Allocate(sizeof(b2DistanceJoint)); + joint = new b2DistanceJoint(def as b2DistanceJointDef); + } + break; + + case e_mouseJoint: + { + //void* mem = allocator->Allocate(sizeof(b2MouseJoint)); + joint = new b2MouseJoint(def as b2MouseJointDef); + } + break; + + case e_prismaticJoint: + { + //void* mem = allocator->Allocate(sizeof(b2PrismaticJoint)); + joint = new b2PrismaticJoint(def as b2PrismaticJointDef); + } + break; + + case e_revoluteJoint: + { + //void* mem = allocator->Allocate(sizeof(b2RevoluteJoint)); + joint = new b2RevoluteJoint(def as b2RevoluteJointDef); + } + break; + + case e_pulleyJoint: + { + //void* mem = allocator->Allocate(sizeof(b2PulleyJoint)); + joint = new b2PulleyJoint(def as b2PulleyJointDef); + } + break; + + case e_gearJoint: + { + //void* mem = allocator->Allocate(sizeof(b2GearJoint)); + joint = new b2GearJoint(def as b2GearJointDef); + } + break; + + case e_lineJoint: + { + //void* mem = allocator->Allocate(sizeof(b2LineJoint)); + joint = new b2LineJoint(def as b2LineJointDef); + } + break; + + case e_weldJoint: + { + //void* mem = allocator->Allocate(sizeof(b2WeldJoint)); + joint = new b2WeldJoint(def as b2WeldJointDef); + } + break; + + case e_frictionJoint: + { + //void* mem = allocator->Allocate(sizeof(b2FrictionJoint)); + joint = new b2FrictionJoint(def as b2FrictionJointDef); + } + break; + + default: + //b2Settings.b2Assert(false); + break; + } + + return joint; + } + + static b2internal function Destroy(joint:b2Joint, allocator:*) : void{ + /*joint->~b2Joint(); + switch (joint.m_type) + { + case e_distanceJoint: + allocator->Free(joint, sizeof(b2DistanceJoint)); + break; + + case e_mouseJoint: + allocator->Free(joint, sizeof(b2MouseJoint)); + break; + + case e_prismaticJoint: + allocator->Free(joint, sizeof(b2PrismaticJoint)); + break; + + case e_revoluteJoint: + allocator->Free(joint, sizeof(b2RevoluteJoint)); + break; + + case e_pulleyJoint: + allocator->Free(joint, sizeof(b2PulleyJoint)); + break; + + case e_gearJoint: + allocator->Free(joint, sizeof(b2GearJoint)); + break; + + case e_lineJoint: + allocator->Free(joint, sizeof(b2LineJoint)); + break; + + case e_weldJoint: + allocator->Free(joint, sizeof(b2WeldJoint)); + break; + + case e_frictionJoint: + allocator->Free(joint, sizeof(b2FrictionJoint)); + break; + + default: + b2Assert(false); + break; + }*/ + } + + /** @private */ + public function b2Joint(def:b2JointDef) { + b2Settings.b2Assert(def.bodyA != def.bodyB); + m_type = def.type; + m_prev = null; + m_next = null; + m_bodyA = def.bodyA; + m_bodyB = def.bodyB; + m_collideConnected = def.collideConnected; + m_islandFlag = false; + m_userData = def.userData; + } + //virtual ~b2Joint() {} + + b2internal virtual function InitVelocityConstraints(step:b2TimeStep) : void{}; + b2internal virtual function SolveVelocityConstraints(step:b2TimeStep) : void { }; + b2internal virtual function FinalizeVelocityConstraints() : void{}; + + // This returns true if the position errors are within tolerance. + b2internal virtual function SolvePositionConstraints(baumgarte:Number):Boolean { return false }; + + b2internal var m_type:int; + b2internal var m_prev:b2Joint; + b2internal var m_next:b2Joint; + b2internal var m_edgeA:b2JointEdge = new b2JointEdge(); + b2internal var m_edgeB:b2JointEdge = new b2JointEdge(); + b2internal var m_bodyA:b2Body; + b2internal var m_bodyB:b2Body; + + b2internal var m_islandFlag:Boolean; + b2internal var m_collideConnected:Boolean; + + private var m_userData:*; + + // Cache here per time step to reduce cache misses. + b2internal var m_localCenterA:b2Vec2 = new b2Vec2(); + b2internal var m_localCenterB:b2Vec2 = new b2Vec2(); + b2internal var m_invMassA:Number; + b2internal var m_invMassB:Number; + b2internal var m_invIA:Number; + b2internal var m_invIB:Number; + + // ENUMS + + // enum b2JointType + static b2internal const e_unknownJoint:int = 0; + static b2internal const e_revoluteJoint:int = 1; + static b2internal const e_prismaticJoint:int = 2; + static b2internal const e_distanceJoint:int = 3; + static b2internal const e_pulleyJoint:int = 4; + static b2internal const e_mouseJoint:int = 5; + static b2internal const e_gearJoint:int = 6; + static b2internal const e_lineJoint:int = 7; + static b2internal const e_weldJoint:int = 8; + static b2internal const e_frictionJoint:int = 9; + + // enum b2LimitState + static b2internal const e_inactiveLimit:int = 0; + static b2internal const e_atLowerLimit:int = 1; + static b2internal const e_atUpperLimit:int = 2; + static b2internal const e_equalLimits:int = 3; + +}; + + + +} diff --git a/srclib/Box2D/Dynamics/Joints/b2JointDef.as b/srclib/Box2D/Dynamics/Joints/b2JointDef.as new file mode 100644 index 00000000..b92723d8 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2JointDef.as @@ -0,0 +1,66 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + + import Box2D.Common.b2internal; + import Box2D.Dynamics.*; +use namespace b2internal; + + +/** +* Joint definitions are used to construct joints. +* @see b2Joint +*/ +public class b2JointDef +{ + + public function b2JointDef() + { + type = b2Joint.e_unknownJoint; + userData = null; + bodyA = null; + bodyB = null; + collideConnected = false; + } + + /** + * The joint type is set automatically for concrete joint types. + */ + public var type:int; + /** + * Use this to attach application specific data to your joints. + */ + public var userData:*; + /** + * The first attached body. + */ + public var bodyA:b2Body; + /** + * The second attached body. + */ + public var bodyB:b2Body; + /** + * Set this flag to true if the attached bodies should collide. + */ + public var collideConnected:Boolean; + +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2JointEdge.as b/srclib/Box2D/Dynamics/Joints/b2JointEdge.as new file mode 100644 index 00000000..3aa54c8c --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2JointEdge.as @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + + import Box2D.Common.b2internal; + import Box2D.Dynamics.*; +use namespace b2internal; + + +/** +* A joint edge is used to connect bodies and joints together +* in a joint graph where each body is a node and each joint +* is an edge. A joint edge belongs to a doubly linked list +* maintained in each attached body. Each joint has two joint +* nodes, one for each attached body. +*/ + +public class b2JointEdge +{ + public function b2JointEdge() {} + + /** Provides quick access to the other body attached. */ + public var other:b2Body; + /** The joint */ + public var joint:b2Joint; + /** The previous joint edge in the body's joint list */ + public var prev:b2JointEdge; + /** The next joint edge in the body's joint list */ + public var next:b2JointEdge; +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2LineJoint.as b/srclib/Box2D/Dynamics/Joints/b2LineJoint.as new file mode 100644 index 00000000..7afc17e4 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2LineJoint.as @@ -0,0 +1,772 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; +use namespace b2internal; + +// Linear constraint (point-to-line) +// d = p2 - p1 = x2 + r2 - x1 - r1 +// C = dot(perp, d) +// Cdot = dot(d, cross(w1, perp)) + dot(perp, v2 + cross(w2, r2) - v1 - cross(w1, r1)) +// = -dot(perp, v1) - dot(cross(d + r1, perp), w1) + dot(perp, v2) + dot(cross(r2, perp), v2) +// J = [-perp, -cross(d + r1, perp), perp, cross(r2,perp)] +// +// K = J * invM * JT +// +// J = [-a -s1 a s2] +// a = perp +// s1 = cross(d + r1, a) = cross(p2 - x1, a) +// s2 = cross(r2, a) = cross(p2 - x2, a) + + +// Motor/Limit linear constraint +// C = dot(ax1, d) +// Cdot = = -dot(ax1, v1) - dot(cross(d + r1, ax1), w1) + dot(ax1, v2) + dot(cross(r2, ax1), v2) +// J = [-ax1 -cross(d+r1,ax1) ax1 cross(r2,ax1)] + +// Block Solver +// We develop a block solver that includes the joint limit. This makes the limit stiff (inelastic) even +// when the mass has poor distribution (leading to large torques about the joint anchor points). +// +// The Jacobian has 3 rows: +// J = [-uT -s1 uT s2] // linear +// [-vT -a1 vT a2] // limit +// +// u = perp +// v = axis +// s1 = cross(d + r1, u), s2 = cross(r2, u) +// a1 = cross(d + r1, v), a2 = cross(r2, v) + +// M * (v2 - v1) = JT * df +// J * v2 = bias +// +// v2 = v1 + invM * JT * df +// J * (v1 + invM * JT * df) = bias +// K * df = bias - J * v1 = -Cdot +// K = J * invM * JT +// Cdot = J * v1 - bias +// +// Now solve for f2. +// df = f2 - f1 +// K * (f2 - f1) = -Cdot +// f2 = invK * (-Cdot) + f1 +// +// Clamp accumulated limit impulse. +// lower: f2(2) = max(f2(2), 0) +// upper: f2(2) = min(f2(2), 0) +// +// Solve for correct f2(1) +// K(1,1) * f2(1) = -Cdot(1) - K(1,2) * f2(2) + K(1,1:2) * f1 +// = -Cdot(1) - K(1,2) * f2(2) + K(1,1) * f1(1) + K(1,2) * f1(2) +// K(1,1) * f2(1) = -Cdot(1) - K(1,2) * (f2(2) - f1(2)) + K(1,1) * f1(1) +// f2(1) = invK(1,1) * (-Cdot(1) - K(1,2) * (f2(2) - f1(2))) + f1(1) +// +// Now compute impulse to be applied: +// df = f2 - f1 + +/** + * A line joint. This joint provides one degree of freedom: translation + * along an axis fixed in body1. You can use a joint limit to restrict + * the range of motion and a joint motor to drive the motion or to + * model joint friction. + * @see b2LineJointDef + */ +public class b2LineJoint extends b2Joint +{ + /** @inheritDoc */ + public override function GetAnchorA():b2Vec2{ + return m_bodyA.GetWorldPoint(m_localAnchor1); + } + /** @inheritDoc */ + public override function GetAnchorB():b2Vec2{ + return m_bodyB.GetWorldPoint(m_localAnchor2); + } + /** @inheritDoc */ + public override function GetReactionForce(inv_dt:Number) : b2Vec2 + { + //return inv_dt * (m_impulse.x * m_perp + (m_motorImpulse + m_impulse.y) * m_axis); + return new b2Vec2( inv_dt * (m_impulse.x * m_perp.x + (m_motorImpulse + m_impulse.y) * m_axis.x), + inv_dt * (m_impulse.x * m_perp.y + (m_motorImpulse + m_impulse.y) * m_axis.y)); + } + + /** @inheritDoc */ + public override function GetReactionTorque(inv_dt:Number) : Number + { + return inv_dt * m_impulse.y; + } + + /** + * Get the current joint translation, usually in meters. + */ + public function GetJointTranslation():Number{ + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + + var p1:b2Vec2 = bA.GetWorldPoint(m_localAnchor1); + var p2:b2Vec2 = bB.GetWorldPoint(m_localAnchor2); + //var d:b2Vec2 = b2Math.SubtractVV(p2, p1); + var dX:Number = p2.x - p1.x; + var dY:Number = p2.y - p1.y; + //b2Vec2 axis = bA->GetWorldVector(m_localXAxis1); + var axis:b2Vec2 = bA.GetWorldVector(m_localXAxis1); + + //float32 translation = b2Dot(d, axis); + var translation:Number = axis.x*dX + axis.y*dY; + return translation; + } + + /** + * Get the current joint translation speed, usually in meters per second. + */ + public function GetJointSpeed():Number{ + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - bA.m_sweep.localCenter.x; + var r1Y:Number = m_localAnchor1.y - bA.m_sweep.localCenter.y; + var tX:Number = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - bB.m_sweep.localCenter.x; + var r2Y:Number = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + //b2Vec2 p1 = bA->m_sweep.c + r1; + var p1X:Number = bA.m_sweep.c.x + r1X; + var p1Y:Number = bA.m_sweep.c.y + r1Y; + //b2Vec2 p2 = bB->m_sweep.c + r2; + var p2X:Number = bB.m_sweep.c.x + r2X; + var p2Y:Number = bB.m_sweep.c.y + r2Y; + //var d:b2Vec2 = b2Math.SubtractVV(p2, p1); + var dX:Number = p2X - p1X; + var dY:Number = p2Y - p1Y; + //b2Vec2 axis = bA->GetWorldVector(m_localXAxis1); + var axis:b2Vec2 = bA.GetWorldVector(m_localXAxis1); + + var v1:b2Vec2 = bA.m_linearVelocity; + var v2:b2Vec2 = bB.m_linearVelocity; + var w1:Number = bA.m_angularVelocity; + var w2:Number = bB.m_angularVelocity; + + //var speed:Number = b2Math.b2Dot(d, b2Math.b2CrossFV(w1, ax1)) + b2Math.b2Dot(ax1, b2Math.SubtractVV( b2Math.SubtractVV( b2Math.AddVV( v2 , b2Math.b2CrossFV(w2, r2)) , v1) , b2Math.b2CrossFV(w1, r1))); + //var b2D:Number = (dX*(-w1 * ax1Y) + dY*(w1 * ax1X)); + //var b2D2:Number = (ax1X * ((( v2.x + (-w2 * r2Y)) - v1.x) - (-w1 * r1Y)) + ax1Y * ((( v2.y + (w2 * r2X)) - v1.y) - (w1 * r1X))); + var speed:Number = (dX*(-w1 * axis.y) + dY*(w1 * axis.x)) + (axis.x * ((( v2.x + (-w2 * r2Y)) - v1.x) - (-w1 * r1Y)) + axis.y * ((( v2.y + (w2 * r2X)) - v1.y) - (w1 * r1X))); + + return speed; + } + + /** + * Is the joint limit enabled? + */ + public function IsLimitEnabled() : Boolean + { + return m_enableLimit; + } + /** + * Enable/disable the joint limit. + */ + public function EnableLimit(flag:Boolean) : void + { + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_enableLimit = flag; + } + /** + * Get the lower joint limit, usually in meters. + */ + public function GetLowerLimit() : Number + { + return m_lowerTranslation; + } + /** + * Get the upper joint limit, usually in meters. + */ + public function GetUpperLimit() : Number + { + return m_upperTranslation; + } + /** + * Set the joint limits, usually in meters. + */ + public function SetLimits(lower:Number, upper:Number) : void + { + //b2Settings.b2Assert(lower <= upper); + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_lowerTranslation = lower; + m_upperTranslation = upper; + } + /** + * Is the joint motor enabled? + */ + public function IsMotorEnabled() : Boolean + { + return m_enableMotor; + } + /** + * Enable/disable the joint motor. + */ + public function EnableMotor(flag:Boolean) : void + { + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_enableMotor = flag; + } + /** + * Set the motor speed, usually in meters per second. + */ + public function SetMotorSpeed(speed:Number) : void + { + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_motorSpeed = speed; + } + /** + * Get the motor speed, usually in meters per second. + */ + public function GetMotorSpeed() :Number + { + return m_motorSpeed; + } + + /** + * Set the maximum motor force, usually in N. + */ + public function SetMaxMotorForce(force:Number) : void + { + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_maxMotorForce = force; + } + + /** + * Get the maximum motor force, usually in N. + */ + public function GetMaxMotorForce():Number + { + return m_maxMotorForce; + } + + /** + * Get the current motor force, usually in N. + */ + public function GetMotorForce() : Number + { + return m_motorImpulse; + } + + + //--------------- Internals Below ------------------- + + /** @private */ + public function b2LineJoint(def:b2LineJointDef){ + super(def); + + var tMat:b2Mat22; + var tX:Number; + var tY:Number; + + m_localAnchor1.SetV(def.localAnchorA); + m_localAnchor2.SetV(def.localAnchorB); + m_localXAxis1.SetV(def.localAxisA); + + //m_localYAxis1 = b2Cross(1.0f, m_localXAxis1); + m_localYAxis1.x = -m_localXAxis1.y; + m_localYAxis1.y = m_localXAxis1.x; + + m_impulse.SetZero(); + m_motorMass = 0.0; + m_motorImpulse = 0.0; + + m_lowerTranslation = def.lowerTranslation; + m_upperTranslation = def.upperTranslation; + m_maxMotorForce = def.maxMotorForce; + m_motorSpeed = def.motorSpeed; + m_enableLimit = def.enableLimit; + m_enableMotor = def.enableMotor; + m_limitState = e_inactiveLimit; + + m_axis.SetZero(); + m_perp.SetZero(); + } + + b2internal override function InitVelocityConstraints(step:b2TimeStep) : void{ + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + var tX:Number; + + m_localCenterA.SetV(bA.GetLocalCenter()); + m_localCenterB.SetV(bB.GetLocalCenter()); + + var xf1:b2Transform = bA.GetTransform(); + var xf2:b2Transform = bB.GetTransform(); + + // Compute the effective masses. + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - m_localCenterA.x; + var r1Y:Number = m_localAnchor1.y - m_localCenterA.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - m_localCenterB.x; + var r2Y:Number = m_localAnchor2.y - m_localCenterB.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + //b2Vec2 d = bB->m_sweep.c + r2 - bA->m_sweep.c - r1; + var dX:Number = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X; + var dY:Number = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y; + + m_invMassA = bA.m_invMass; + m_invMassB = bB.m_invMass; + m_invIA = bA.m_invI; + m_invIB = bB.m_invI; + + // Compute motor Jacobian and effective mass. + { + m_axis.SetV(b2Math.MulMV(xf1.R, m_localXAxis1)); + //m_a1 = b2Math.b2Cross(d + r1, m_axis); + m_a1 = (dX + r1X) * m_axis.y - (dY + r1Y) * m_axis.x; + //m_a2 = b2Math.b2Cross(r2, m_axis); + m_a2 = r2X * m_axis.y - r2Y * m_axis.x; + + m_motorMass = m_invMassA + m_invMassB + m_invIA * m_a1 * m_a1 + m_invIB * m_a2 * m_a2; + m_motorMass = m_motorMass > Number.MIN_VALUE?1.0 / m_motorMass:0.0; + } + + // Prismatic constraint. + { + m_perp.SetV(b2Math.MulMV(xf1.R, m_localYAxis1)); + //m_s1 = b2Math.b2Cross(d + r1, m_perp); + m_s1 = (dX + r1X) * m_perp.y - (dY + r1Y) * m_perp.x; + //m_s2 = b2Math.b2Cross(r2, m_perp); + m_s2 = r2X * m_perp.y - r2Y * m_perp.x; + + var m1:Number = m_invMassA; + var m2:Number = m_invMassB; + var i1:Number = m_invIA; + var i2:Number = m_invIB; + + m_K.col1.x = m1 + m2 + i1 * m_s1 * m_s1 + i2 * m_s2 * m_s2; + m_K.col1.y = i1 * m_s1 * m_a1 + i2 * m_s2 * m_a2; + m_K.col2.x = m_K.col1.y; + m_K.col2.y = m1 + m2 + i1 * m_a1 * m_a1 + i2 * m_a2 * m_a2; + } + + // Compute motor and limit terms + if (m_enableLimit) + { + //float32 jointTranslation = b2Dot(m_axis, d); + var jointTransition:Number = m_axis.x * dX + m_axis.y * dY; + if (b2Math.Abs(m_upperTranslation - m_lowerTranslation) < 2.0 * b2Settings.b2_linearSlop) + { + m_limitState = e_equalLimits; + } + else if (jointTransition <= m_lowerTranslation) + { + if (m_limitState != e_atLowerLimit) + { + m_limitState = e_atLowerLimit; + m_impulse.y = 0.0; + } + } + else if (jointTransition >= m_upperTranslation) + { + if (m_limitState != e_atUpperLimit) + { + m_limitState = e_atUpperLimit; + m_impulse.y = 0.0; + } + } + else + { + m_limitState = e_inactiveLimit; + m_impulse.y = 0.0; + } + } + else + { + m_limitState = e_inactiveLimit; + } + + if (m_enableMotor == false) + { + m_motorImpulse = 0.0 + } + + if (step.warmStarting) + { + // Account for variable time step. + m_impulse.x *= step.dtRatio; + m_impulse.y *= step.dtRatio; + m_motorImpulse *= step.dtRatio; + + //b2Vec2 P = m_impulse.x * m_perp + (m_motorImpulse + m_impulse.z) * m_axis; + var PX:Number = m_impulse.x * m_perp.x + (m_motorImpulse + m_impulse.y) * m_axis.x; + var PY:Number = m_impulse.x * m_perp.y + (m_motorImpulse + m_impulse.y) * m_axis.y; + var L1:Number = m_impulse.x * m_s1 + (m_motorImpulse + m_impulse.y) * m_a1; + var L2:Number = m_impulse.x * m_s2 + (m_motorImpulse + m_impulse.y) * m_a2; + + //bA->m_linearVelocity -= m_invMassA * P; + bA.m_linearVelocity.x -= m_invMassA * PX; + bA.m_linearVelocity.y -= m_invMassA * PY; + //bA->m_angularVelocity -= m_invIA * L1; + bA.m_angularVelocity -= m_invIA * L1; + + //bB->m_linearVelocity += m_invMassB * P; + bB.m_linearVelocity.x += m_invMassB * PX; + bB.m_linearVelocity.y += m_invMassB * PY; + //bB->m_angularVelocity += m_invIB * L2; + bB.m_angularVelocity += m_invIB * L2; + } + else + { + m_impulse.SetZero(); + m_motorImpulse = 0.0; + } + } + + b2internal override function SolveVelocityConstraints(step:b2TimeStep) : void{ + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var v1:b2Vec2 = bA.m_linearVelocity; + var w1:Number = bA.m_angularVelocity; + var v2:b2Vec2 = bB.m_linearVelocity; + var w2:Number = bB.m_angularVelocity; + + var PX:Number; + var PY:Number; + var L1:Number; + var L2:Number; + + // Solve linear motor constraint + if (m_enableMotor && m_limitState != e_equalLimits) + { + //float32 Cdot = b2Dot(m_axis, v2 - v1) + m_a2 * w2 - m_a1 * w1; + var Cdot:Number = m_axis.x * (v2.x -v1.x) + m_axis.y * (v2.y - v1.y) + m_a2 * w2 - m_a1 * w1; + var impulse:Number = m_motorMass * (m_motorSpeed - Cdot); + var oldImpulse:Number = m_motorImpulse; + var maxImpulse:Number = step.dt * m_maxMotorForce; + m_motorImpulse = b2Math.Clamp(m_motorImpulse + impulse, -maxImpulse, maxImpulse); + impulse = m_motorImpulse - oldImpulse; + + PX = impulse * m_axis.x; + PY = impulse * m_axis.y; + L1 = impulse * m_a1; + L2 = impulse * m_a2; + + v1.x -= m_invMassA * PX; + v1.y -= m_invMassA * PY; + w1 -= m_invIA * L1; + + v2.x += m_invMassB * PX; + v2.y += m_invMassB * PY; + w2 += m_invIB * L2; + } + + //Cdot1 = b2Dot(m_perp, v2 - v1) + m_s2 * w2 - m_s1 * w1; + var Cdot1:Number = m_perp.x * (v2.x - v1.x) + m_perp.y * (v2.y - v1.y) + m_s2 * w2 - m_s1 * w1; + + if (m_enableLimit && m_limitState != e_inactiveLimit) + { + // Solve prismatic and limit constraint in block form + //Cdot2 = b2Dot(m_axis, v2 - v1) + m_a2 * w2 - m_a1 * w1; + var Cdot2:Number = m_axis.x * (v2.x - v1.x) + m_axis.y * (v2.y - v1.y) + m_a2 * w2 - m_a1 * w1; + + var f1:b2Vec2 = m_impulse.Copy(); + var df:b2Vec2 = m_K.Solve(new b2Vec2(), -Cdot1, -Cdot2); + + m_impulse.Add(df); + + if (m_limitState == e_atLowerLimit) + { + m_impulse.y = b2Math.Max(m_impulse.y, 0.0); + } + else if (m_limitState == e_atUpperLimit) + { + m_impulse.y = b2Math.Min(m_impulse.y, 0.0); + } + + // f2(1) = invK(1,1) * (-Cdot(1) - K(1,3) * (f2(2) - f1(2))) + f1(1) + var b:Number = -Cdot1 - (m_impulse.y - f1.y) * m_K.col2.x; + var f2r:Number; + if (m_K.col1.x != 0.0) + { + f2r = b / m_K.col1.x + f1.x; + }else { + f2r = f1.x; + } + m_impulse.x = f2r; + + df.x = m_impulse.x - f1.x; + df.y = m_impulse.y - f1.y; + + PX = df.x * m_perp.x + df.y * m_axis.x; + PY = df.x * m_perp.y + df.y * m_axis.y; + L1 = df.x * m_s1 + df.y * m_a1; + L2 = df.x * m_s2 + df.y * m_a2; + + v1.x -= m_invMassA * PX; + v1.y -= m_invMassA * PY; + w1 -= m_invIA * L1; + + v2.x += m_invMassB * PX; + v2.y += m_invMassB * PY; + w2 += m_invIB * L2; + } + else + { + // Limit is inactive, just solve the prismatic constraint in block form. + var df2:Number; + if (m_K.col1.x != 0.0) + { + df2 = ( -Cdot1) / m_K.col1.x; + }else { + df2 = 0.0; + } + m_impulse.x += df2; + + PX = df2 * m_perp.x; + PY = df2 * m_perp.y; + L1 = df2 * m_s1; + L2 = df2 * m_s2; + + v1.x -= m_invMassA * PX; + v1.y -= m_invMassA * PY; + w1 -= m_invIA * L1; + + v2.x += m_invMassB * PX; + v2.y += m_invMassB * PY; + w2 += m_invIB * L2; + } + + bA.m_linearVelocity.SetV(v1); + bA.m_angularVelocity = w1; + bB.m_linearVelocity.SetV(v2); + bB.m_angularVelocity = w2; + } + + b2internal override function SolvePositionConstraints(baumgarte:Number ):Boolean + { + //B2_NOT_USED(baumgarte); + + + var limitC:Number; + var oldLimitImpulse:Number; + + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var c1:b2Vec2 = bA.m_sweep.c; + var a1:Number = bA.m_sweep.a; + + var c2:b2Vec2 = bB.m_sweep.c; + var a2:Number = bB.m_sweep.a; + + var tMat:b2Mat22; + var tX:Number; + + var m1:Number; + var m2:Number; + var i1:Number; + var i2:Number; + + // Solve linear limit constraint + var linearError:Number = 0.0; + var angularError:Number = 0.0; + var active:Boolean = false; + var C2:Number = 0.0; + + var R1:b2Mat22 = b2Mat22.FromAngle(a1); + var R2:b2Mat22 = b2Mat22.FromAngle(a2); + + //b2Vec2 r1 = b2Mul(R1, m_localAnchor1 - m_localCenter1); + tMat = R1; + var r1X:Number = m_localAnchor1.x - m_localCenterA.x; + var r1Y:Number = m_localAnchor1.y - m_localCenterA.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(R2, m_localAnchor2 - m_localCenter2); + tMat = R2; + var r2X:Number = m_localAnchor2.x - m_localCenterB.x; + var r2Y:Number = m_localAnchor2.y - m_localCenterB.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + var dX:Number = c2.x + r2X - c1.x - r1X; + var dY:Number = c2.y + r2Y - c1.y - r1Y; + + if (m_enableLimit) + { + m_axis = b2Math.MulMV(R1, m_localXAxis1); + + //m_a1 = b2Math.b2Cross(d + r1, m_axis); + m_a1 = (dX + r1X) * m_axis.y - (dY + r1Y) * m_axis.x; + //m_a2 = b2Math.b2Cross(r2, m_axis); + m_a2 = r2X * m_axis.y - r2Y * m_axis.x; + + var translation:Number = m_axis.x * dX + m_axis.y * dY; + if (b2Math.Abs(m_upperTranslation - m_lowerTranslation) < 2.0 * b2Settings.b2_linearSlop) + { + // Prevent large angular corrections. + C2 = b2Math.Clamp(translation, -b2Settings.b2_maxLinearCorrection, b2Settings.b2_maxLinearCorrection); + linearError = b2Math.Abs(translation); + active = true; + } + else if (translation <= m_lowerTranslation) + { + // Prevent large angular corrections and allow some slop. + C2 = b2Math.Clamp(translation - m_lowerTranslation + b2Settings.b2_linearSlop, -b2Settings.b2_maxLinearCorrection, 0.0); + linearError = m_lowerTranslation - translation; + active = true; + } + else if (translation >= m_upperTranslation) + { + // Prevent large angular corrections and allow some slop. + C2 = b2Math.Clamp(translation - m_upperTranslation + b2Settings.b2_linearSlop, 0.0, b2Settings.b2_maxLinearCorrection); + linearError = translation - m_upperTranslation; + active = true; + } + } + + m_perp = b2Math.MulMV(R1, m_localYAxis1); + + //m_s1 = b2Cross(d + r1, m_perp); + m_s1 = (dX + r1X) * m_perp.y - (dY + r1Y) * m_perp.x; + //m_s2 = b2Cross(r2, m_perp); + m_s2 = r2X * m_perp.y - r2Y * m_perp.x; + + var impulse:b2Vec2 = new b2Vec2(); + var C1:Number = m_perp.x * dX + m_perp.y * dY; + + linearError = b2Math.Max(linearError, b2Math.Abs(C1)); + angularError = 0.0; + + if (active) + { + m1 = m_invMassA; + m2 = m_invMassB; + i1 = m_invIA; + i2 = m_invIB; + + m_K.col1.x = m1 + m2 + i1 * m_s1 * m_s1 + i2 * m_s2 * m_s2; + m_K.col1.y = i1 * m_s1 * m_a1 + i2 * m_s2 * m_a2; + m_K.col2.x = m_K.col1.y; + m_K.col2.y = m1 + m2 + i1 * m_a1 * m_a1 + i2 * m_a2 * m_a2; + + m_K.Solve(impulse, -C1, -C2); + } + else + { + m1 = m_invMassA; + m2 = m_invMassB; + i1 = m_invIA; + i2 = m_invIB; + + var k11:Number = m1 + m2 + i1 * m_s1 * m_s1 + i2 * m_s2 * m_s2; + + var impulse1:Number; + if (k11 != 0.0) + { + impulse1 = ( -C1) / k11; + }else { + impulse1 = 0.0; + } + impulse.x = impulse1; + impulse.y = 0.0; + } + + var PX:Number = impulse.x * m_perp.x + impulse.y * m_axis.x; + var PY:Number = impulse.x * m_perp.y + impulse.y * m_axis.y; + var L1:Number = impulse.x * m_s1 + impulse.y * m_a1; + var L2:Number = impulse.x * m_s2 + impulse.y * m_a2; + + c1.x -= m_invMassA * PX; + c1.y -= m_invMassA * PY; + a1 -= m_invIA * L1; + + c2.x += m_invMassB * PX; + c2.y += m_invMassB * PY; + a2 += m_invIB * L2; + + // TODO_ERIN remove need for this + //bA.m_sweep.c = c1; //Already done by reference + bA.m_sweep.a = a1; + //bB.m_sweep.c = c2; //Already done by reference + bB.m_sweep.a = a2; + bA.SynchronizeTransform(); + bB.SynchronizeTransform(); + + return linearError <= b2Settings.b2_linearSlop && angularError <= b2Settings.b2_angularSlop; + + } + + b2internal var m_localAnchor1:b2Vec2 = new b2Vec2(); + b2internal var m_localAnchor2:b2Vec2 = new b2Vec2(); + b2internal var m_localXAxis1:b2Vec2 = new b2Vec2(); + private var m_localYAxis1:b2Vec2 = new b2Vec2(); + + private var m_axis:b2Vec2 = new b2Vec2(); + private var m_perp:b2Vec2 = new b2Vec2(); + private var m_s1:Number; + private var m_s2:Number; + private var m_a1:Number; + private var m_a2:Number; + + private var m_K:b2Mat22 = new b2Mat22(); + private var m_impulse:b2Vec2 = new b2Vec2(); + + private var m_motorMass:Number; // effective mass for motor/limit translational constraint. + private var m_motorImpulse:Number; + + private var m_lowerTranslation:Number; + private var m_upperTranslation:Number; + private var m_maxMotorForce:Number; + private var m_motorSpeed:Number; + + private var m_enableLimit:Boolean; + private var m_enableMotor:Boolean; + private var m_limitState:int; +}; + +} diff --git a/srclib/Box2D/Dynamics/Joints/b2LineJointDef.as b/srclib/Box2D/Dynamics/Joints/b2LineJointDef.as new file mode 100644 index 00000000..569037da --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2LineJointDef.as @@ -0,0 +1,110 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + import Box2D.Dynamics.*; + +use namespace b2internal; + + +/** + * Line joint definition. This requires defining a line of + * motion using an axis and an anchor point. The definition uses local + * anchor points and a local axis so that the initial configuration + * can violate the constraint slightly. The joint translation is zero + * when the local anchor points coincide in world space. Using local + * anchors and a local axis helps when saving and loading a game. + * @see b2LineJoint + */ +public class b2LineJointDef extends b2JointDef +{ + public function b2LineJointDef() + { + type = b2Joint.e_lineJoint; + //localAnchor1.SetZero(); + //localAnchor2.SetZero(); + localAxisA.Set(1.0, 0.0); + enableLimit = false; + lowerTranslation = 0.0; + upperTranslation = 0.0; + enableMotor = false; + maxMotorForce = 0.0; + motorSpeed = 0.0; + } + + public function Initialize(bA:b2Body, bB:b2Body, anchor:b2Vec2, axis:b2Vec2) : void + { + bodyA = bA; + bodyB = bB; + localAnchorA = bodyA.GetLocalPoint(anchor); + localAnchorB = bodyB.GetLocalPoint(anchor); + localAxisA = bodyA.GetLocalVector(axis); + } + + /** + * The local anchor point relative to bodyA's origin. + */ + public var localAnchorA:b2Vec2 = new b2Vec2(); + + /** + * The local anchor point relative to bodyB's origin. + */ + public var localAnchorB:b2Vec2 = new b2Vec2(); + + /** + * The local translation axis in bodyA. + */ + public var localAxisA:b2Vec2 = new b2Vec2(); + + /** + * Enable/disable the joint limit. + */ + public var enableLimit:Boolean; + + /** + * The lower translation limit, usually in meters. + */ + public var lowerTranslation:Number; + + /** + * The upper translation limit, usually in meters. + */ + public var upperTranslation:Number; + + /** + * Enable/disable the joint motor. + */ + public var enableMotor:Boolean; + + /** + * The maximum motor torque, usually in N-m. + */ + public var maxMotorForce:Number; + + /** + * The desired motor speed in radians per second. + */ + public var motorSpeed:Number; + + +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2MouseJoint.as b/srclib/Box2D/Dynamics/Joints/b2MouseJoint.as new file mode 100644 index 00000000..a4171d1e --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2MouseJoint.as @@ -0,0 +1,291 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; +use namespace b2internal; + + +// p = attached point, m = mouse point +// C = p - m +// Cdot = v +// = v + cross(w, r) +// J = [I r_skew] +// Identity used: +// w k % (rx i + ry j) = w * (-ry i + rx j) + +/** +* A mouse joint is used to make a point on a body track a +* specified world point. This a soft constraint with a maximum +* force. This allows the constraint to stretch and without +* applying huge forces. +* Note: this joint is not fully documented as it is intended primarily +* for the testbed. See that for more instructions. +* @see b2MouseJointDef +*/ + +public class b2MouseJoint extends b2Joint +{ + /** @inheritDoc */ + public override function GetAnchorA():b2Vec2{ + return m_target; + } + /** @inheritDoc */ + public override function GetAnchorB():b2Vec2{ + return m_bodyB.GetWorldPoint(m_localAnchor); + } + /** @inheritDoc */ + public override function GetReactionForce(inv_dt:Number):b2Vec2 + { + return new b2Vec2(inv_dt * m_impulse.x, inv_dt * m_impulse.y); + } + /** @inheritDoc */ + public override function GetReactionTorque(inv_dt:Number):Number + { + return 0.0; + } + + public function GetTarget():b2Vec2 + { + return m_target; + } + + /** + * Use this to update the target point. + */ + public function SetTarget(target:b2Vec2) : void{ + if (m_bodyB.IsAwake() == false){ + m_bodyB.SetAwake(true); + } + m_target = target; + } + + /// Get the maximum force in Newtons. + public function GetMaxForce():Number + { + return m_maxForce; + } + + /// Set the maximum force in Newtons. + public function SetMaxForce(maxForce:Number):void + { + m_maxForce = maxForce; + } + + /// Get frequency in Hz + public function GetFrequency():Number + { + return m_frequencyHz; + } + + /// Set the frequency in Hz + public function SetFrequency(hz:Number):void + { + m_frequencyHz = hz; + } + + /// Get damping ratio + public function GetDampingRatio():Number + { + return m_dampingRatio; + } + + /// Set damping ratio + public function SetDampingRatio(ratio:Number):void + { + m_dampingRatio = ratio; + } + + //--------------- Internals Below ------------------- + + /** @private */ + public function b2MouseJoint(def:b2MouseJointDef){ + super(def); + + //b2Settings.b2Assert(def.target.IsValid()); + //b2Settings.b2Assert(b2Math.b2IsValid(def.maxForce) && def.maxForce > 0.0); + //b2Settings.b2Assert(b2Math.b2IsValid(def.frequencyHz) && def.frequencyHz > 0.0); + //b2Settings.b2Assert(b2Math.b2IsValid(def.dampingRatio) && def.dampingRatio > 0.0); + + m_target.SetV(def.target); + //m_localAnchor = b2MulT(m_bodyB.m_xf, m_target); + var tX:Number = m_target.x - m_bodyB.m_xf.position.x; + var tY:Number = m_target.y - m_bodyB.m_xf.position.y; + var tMat:b2Mat22 = m_bodyB.m_xf.R; + m_localAnchor.x = (tX * tMat.col1.x + tY * tMat.col1.y); + m_localAnchor.y = (tX * tMat.col2.x + tY * tMat.col2.y); + + m_maxForce = def.maxForce; + m_impulse.SetZero(); + + m_frequencyHz = def.frequencyHz; + m_dampingRatio = def.dampingRatio; + + m_beta = 0.0; + m_gamma = 0.0; + } + + // Presolve vars + private var K:b2Mat22 = new b2Mat22(); + private var K1:b2Mat22 = new b2Mat22(); + private var K2:b2Mat22 = new b2Mat22(); + b2internal override function InitVelocityConstraints(step:b2TimeStep): void{ + var b:b2Body = m_bodyB; + + var mass:Number = b.GetMass(); + + // Frequency + var omega:Number = 2.0 * Math.PI * m_frequencyHz; + + // Damping co-efficient + var d:Number = 2.0 * mass * m_dampingRatio * omega; + + // Spring stiffness + var k:Number = mass * omega * omega; + + // magic formulas + // gamma has units of inverse mass + // beta hs units of inverse time + //b2Settings.b2Assert(d + step.dt * k > Number.MIN_VALUE) + m_gamma = step.dt * (d + step.dt * k); + m_gamma = m_gamma != 0 ? 1 / m_gamma:0.0; + m_beta = step.dt * k * m_gamma; + + var tMat:b2Mat22; + + // Compute the effective mass matrix. + //b2Vec2 r = b2Mul(b->m_xf.R, m_localAnchor - b->GetLocalCenter()); + tMat = b.m_xf.R; + var rX:Number = m_localAnchor.x - b.m_sweep.localCenter.x; + var rY:Number = m_localAnchor.y - b.m_sweep.localCenter.y; + var tX:Number = (tMat.col1.x * rX + tMat.col2.x * rY); + rY = (tMat.col1.y * rX + tMat.col2.y * rY); + rX = tX; + + // K = [(1/m1 + 1/m2) * eye(2) - skew(r1) * invI1 * skew(r1) - skew(r2) * invI2 * skew(r2)] + // = [1/m1+1/m2 0 ] + invI1 * [r1.y*r1.y -r1.x*r1.y] + invI2 * [r1.y*r1.y -r1.x*r1.y] + // [ 0 1/m1+1/m2] [-r1.x*r1.y r1.x*r1.x] [-r1.x*r1.y r1.x*r1.x] + var invMass:Number = b.m_invMass; + var invI:Number = b.m_invI; + + //b2Mat22 K1; + K1.col1.x = invMass; K1.col2.x = 0.0; + K1.col1.y = 0.0; K1.col2.y = invMass; + + //b2Mat22 K2; + K2.col1.x = invI * rY * rY; K2.col2.x = -invI * rX * rY; + K2.col1.y = -invI * rX * rY; K2.col2.y = invI * rX * rX; + + //b2Mat22 K = K1 + K2; + K.SetM(K1); + K.AddM(K2); + K.col1.x += m_gamma; + K.col2.y += m_gamma; + + //m_ptpMass = K.GetInverse(); + K.GetInverse(m_mass); + + //m_C = b.m_position + r - m_target; + m_C.x = b.m_sweep.c.x + rX - m_target.x; + m_C.y = b.m_sweep.c.y + rY - m_target.y; + + // Cheat with some damping + b.m_angularVelocity *= 0.98; + + // Warm starting. + m_impulse.x *= step.dtRatio; + m_impulse.y *= step.dtRatio; + //b.m_linearVelocity += invMass * m_impulse; + b.m_linearVelocity.x += invMass * m_impulse.x; + b.m_linearVelocity.y += invMass * m_impulse.y; + //b.m_angularVelocity += invI * b2Cross(r, m_impulse); + b.m_angularVelocity += invI * (rX * m_impulse.y - rY * m_impulse.x); + } + + b2internal override function SolveVelocityConstraints(step:b2TimeStep) : void{ + var b:b2Body = m_bodyB; + + var tMat:b2Mat22; + var tX:Number; + var tY:Number; + + // Compute the effective mass matrix. + //b2Vec2 r = b2Mul(b->m_xf.R, m_localAnchor - b->GetLocalCenter()); + tMat = b.m_xf.R; + var rX:Number = m_localAnchor.x - b.m_sweep.localCenter.x; + var rY:Number = m_localAnchor.y - b.m_sweep.localCenter.y; + tX = (tMat.col1.x * rX + tMat.col2.x * rY); + rY = (tMat.col1.y * rX + tMat.col2.y * rY); + rX = tX; + + // Cdot = v + cross(w, r) + //b2Vec2 Cdot = b->m_linearVelocity + b2Cross(b->m_angularVelocity, r); + var CdotX:Number = b.m_linearVelocity.x + (-b.m_angularVelocity * rY); + var CdotY:Number = b.m_linearVelocity.y + (b.m_angularVelocity * rX); + //b2Vec2 impulse = - b2Mul(m_mass, Cdot + m_beta * m_C + m_gamma * m_impulse); + tMat = m_mass; + tX = CdotX + m_beta * m_C.x + m_gamma * m_impulse.x; + tY = CdotY + m_beta * m_C.y + m_gamma * m_impulse.y; + var impulseX:Number = -(tMat.col1.x * tX + tMat.col2.x * tY); + var impulseY:Number = -(tMat.col1.y * tX + tMat.col2.y * tY); + + var oldImpulseX:Number = m_impulse.x; + var oldImpulseY:Number = m_impulse.y; + //m_impulse += impulse; + m_impulse.x += impulseX; + m_impulse.y += impulseY; + var maxImpulse:Number = step.dt * m_maxForce; + if (m_impulse.LengthSquared() > maxImpulse*maxImpulse) + { + //m_impulse *= m_maxImpulse / m_impulse.Length(); + m_impulse.Multiply(maxImpulse / m_impulse.Length()); + } + //impulse = m_impulse - oldImpulse; + impulseX = m_impulse.x - oldImpulseX; + impulseY = m_impulse.y - oldImpulseY; + + //b->m_linearVelocity += b->m_invMass * impulse; + b.m_linearVelocity.x += b.m_invMass * impulseX; + b.m_linearVelocity.y += b.m_invMass * impulseY; + //b->m_angularVelocity += b->m_invI * b2Cross(r, P); + b.m_angularVelocity += b.m_invI * (rX * impulseY - rY * impulseX); + } + + b2internal override function SolvePositionConstraints(baumgarte:Number):Boolean { + //B2_NOT_USED(baumgarte); + return true; + } + + private var m_localAnchor:b2Vec2 = new b2Vec2(); + private var m_target:b2Vec2 = new b2Vec2(); + private var m_impulse:b2Vec2 = new b2Vec2(); + + private var m_mass:b2Mat22 = new b2Mat22(); // effective mass for point-to-point constraint. + private var m_C:b2Vec2 = new b2Vec2(); // position error + private var m_maxForce:Number; + private var m_frequencyHz:Number; + private var m_dampingRatio:Number; + private var m_beta:Number; // bias factor + private var m_gamma:Number; // softness +}; + +} diff --git a/srclib/Box2D/Dynamics/Joints/b2MouseJointDef.as b/srclib/Box2D/Dynamics/Joints/b2MouseJointDef.as new file mode 100644 index 00000000..a6f9734e --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2MouseJointDef.as @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + +use namespace b2internal; + + +/** +* Mouse joint definition. This requires a world target point, +* tuning parameters, and the time step. +* @see b2MouseJoint +*/ +public class b2MouseJointDef extends b2JointDef +{ + public function b2MouseJointDef() + { + type = b2Joint.e_mouseJoint; + maxForce = 0.0; + frequencyHz = 5.0; + dampingRatio = 0.7; + } + + /** + * The initial world target point. This is assumed + * to coincide with the body anchor initially. + */ + public var target:b2Vec2 = new b2Vec2(); + /** + * The maximum constraint force that can be exerted + * to move the candidate body. Usually you will express + * as some multiple of the weight (multiplier * mass * gravity). + */ + public var maxForce:Number; + /** + * The response speed. + */ + public var frequencyHz:Number; + /** + * The damping ratio. 0 = no damping, 1 = critical damping. + */ + public var dampingRatio:Number; +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2PrismaticJoint.as b/srclib/Box2D/Dynamics/Joints/b2PrismaticJoint.as new file mode 100644 index 00000000..77b3a899 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2PrismaticJoint.as @@ -0,0 +1,781 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; +use namespace b2internal; + + +// Linear constraint (point-to-line) +// d = p2 - p1 = x2 + r2 - x1 - r1 +// C = dot(perp, d) +// Cdot = dot(d, cross(w1, perp)) + dot(perp, v2 + cross(w2, r2) - v1 - cross(w1, r1)) +// = -dot(perp, v1) - dot(cross(d + r1, perp), w1) + dot(perp, v2) + dot(cross(r2, perp), v2) +// J = [-perp, -cross(d + r1, perp), perp, cross(r2,perp)] +// +// Angular constraint +// C = a2 - a1 + a_initial +// Cdot = w2 - w1 +// J = [0 0 -1 0 0 1] +// +// K = J * invM * JT +// +// J = [-a -s1 a s2] +// [0 -1 0 1] +// a = perp +// s1 = cross(d + r1, a) = cross(p2 - x1, a) +// s2 = cross(r2, a) = cross(p2 - x2, a) + +// Motor/Limit linear constraint +// C = dot(ax1, d) +// Cdot = = -dot(ax1, v1) - dot(cross(d + r1, ax1), w1) + dot(ax1, v2) + dot(cross(r2, ax1), v2) +// J = [-ax1 -cross(d+r1,ax1) ax1 cross(r2,ax1)] + +// Block Solver +// We develop a block solver that includes the joint limit. This makes the limit stiff (inelastic) even +// when the mass has poor distribution (leading to large torques about the joint anchor points). +// +// The Jacobian has 3 rows: +// J = [-uT -s1 uT s2] // linear +// [0 -1 0 1] // angular +// [-vT -a1 vT a2] // limit +// +// u = perp +// v = axis +// s1 = cross(d + r1, u), s2 = cross(r2, u) +// a1 = cross(d + r1, v), a2 = cross(r2, v) + +// M * (v2 - v1) = JT * df +// J * v2 = bias +// +// v2 = v1 + invM * JT * df +// J * (v1 + invM * JT * df) = bias +// K * df = bias - J * v1 = -Cdot +// K = J * invM * JT +// Cdot = J * v1 - bias +// +// Now solve for f2. +// df = f2 - f1 +// K * (f2 - f1) = -Cdot +// f2 = invK * (-Cdot) + f1 +// +// Clamp accumulated limit impulse. +// lower: f2(3) = max(f2(3), 0) +// upper: f2(3) = min(f2(3), 0) +// +// Solve for correct f2(1:2) +// K(1:2, 1:2) * f2(1:2) = -Cdot(1:2) - K(1:2,3) * f2(3) + K(1:2,1:3) * f1 +// = -Cdot(1:2) - K(1:2,3) * f2(3) + K(1:2,1:2) * f1(1:2) + K(1:2,3) * f1(3) +// K(1:2, 1:2) * f2(1:2) = -Cdot(1:2) - K(1:2,3) * (f2(3) - f1(3)) + K(1:2,1:2) * f1(1:2) +// f2(1:2) = invK(1:2,1:2) * (-Cdot(1:2) - K(1:2,3) * (f2(3) - f1(3))) + f1(1:2) +// +// Now compute impulse to be applied: +// df = f2 - f1 + +/** +* A prismatic joint. This joint provides one degree of freedom: translation +* along an axis fixed in body1. Relative rotation is prevented. You can +* use a joint limit to restrict the range of motion and a joint motor to +* drive the motion or to model joint friction. +* @see b2PrismaticJointDef +*/ +public class b2PrismaticJoint extends b2Joint +{ + /** @inheritDoc */ + public override function GetAnchorA():b2Vec2{ + return m_bodyA.GetWorldPoint(m_localAnchor1); + } + /** @inheritDoc */ + public override function GetAnchorB():b2Vec2{ + return m_bodyB.GetWorldPoint(m_localAnchor2); + } + /** @inheritDoc */ + public override function GetReactionForce(inv_dt:Number) : b2Vec2 + { + //return inv_dt * (m_impulse.x * m_perp + (m_motorImpulse + m_impulse.z) * m_axis); + return new b2Vec2( inv_dt * (m_impulse.x * m_perp.x + (m_motorImpulse + m_impulse.z) * m_axis.x), + inv_dt * (m_impulse.x * m_perp.y + (m_motorImpulse + m_impulse.z) * m_axis.y)); + } + + /** @inheritDoc */ + public override function GetReactionTorque(inv_dt:Number) : Number + { + return inv_dt * m_impulse.y; + } + + /** + * Get the current joint translation, usually in meters. + */ + public function GetJointTranslation():Number{ + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + + var p1:b2Vec2 = bA.GetWorldPoint(m_localAnchor1); + var p2:b2Vec2 = bB.GetWorldPoint(m_localAnchor2); + //var d:b2Vec2 = b2Math.SubtractVV(p2, p1); + var dX:Number = p2.x - p1.x; + var dY:Number = p2.y - p1.y; + //b2Vec2 axis = bA->GetWorldVector(m_localXAxis1); + var axis:b2Vec2 = bA.GetWorldVector(m_localXAxis1); + + //float32 translation = b2Dot(d, axis); + var translation:Number = axis.x*dX + axis.y*dY; + return translation; + } + + /** + * Get the current joint translation speed, usually in meters per second. + */ + public function GetJointSpeed():Number{ + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - bA.m_sweep.localCenter.x; + var r1Y:Number = m_localAnchor1.y - bA.m_sweep.localCenter.y; + var tX:Number = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - bB.m_sweep.localCenter.x; + var r2Y:Number = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + //b2Vec2 p1 = bA->m_sweep.c + r1; + var p1X:Number = bA.m_sweep.c.x + r1X; + var p1Y:Number = bA.m_sweep.c.y + r1Y; + //b2Vec2 p2 = bB->m_sweep.c + r2; + var p2X:Number = bB.m_sweep.c.x + r2X; + var p2Y:Number = bB.m_sweep.c.y + r2Y; + //var d:b2Vec2 = b2Math.SubtractVV(p2, p1); + var dX:Number = p2X - p1X; + var dY:Number = p2Y - p1Y; + //b2Vec2 axis = bA->GetWorldVector(m_localXAxis1); + var axis:b2Vec2 = bA.GetWorldVector(m_localXAxis1); + + var v1:b2Vec2 = bA.m_linearVelocity; + var v2:b2Vec2 = bB.m_linearVelocity; + var w1:Number = bA.m_angularVelocity; + var w2:Number = bB.m_angularVelocity; + + //var speed:Number = b2Math.b2Dot(d, b2Math.b2CrossFV(w1, ax1)) + b2Math.b2Dot(ax1, b2Math.SubtractVV( b2Math.SubtractVV( b2Math.AddVV( v2 , b2Math.b2CrossFV(w2, r2)) , v1) , b2Math.b2CrossFV(w1, r1))); + //var b2D:Number = (dX*(-w1 * ax1Y) + dY*(w1 * ax1X)); + //var b2D2:Number = (ax1X * ((( v2.x + (-w2 * r2Y)) - v1.x) - (-w1 * r1Y)) + ax1Y * ((( v2.y + (w2 * r2X)) - v1.y) - (w1 * r1X))); + var speed:Number = (dX*(-w1 * axis.y) + dY*(w1 * axis.x)) + (axis.x * ((( v2.x + (-w2 * r2Y)) - v1.x) - (-w1 * r1Y)) + axis.y * ((( v2.y + (w2 * r2X)) - v1.y) - (w1 * r1X))); + + return speed; + } + + /** + * Is the joint limit enabled? + */ + public function IsLimitEnabled() : Boolean + { + return m_enableLimit; + } + /** + * Enable/disable the joint limit. + */ + public function EnableLimit(flag:Boolean) : void + { + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_enableLimit = flag; + } + /** + * Get the lower joint limit, usually in meters. + */ + public function GetLowerLimit() : Number + { + return m_lowerTranslation; + } + /** + * Get the upper joint limit, usually in meters. + */ + public function GetUpperLimit() : Number + { + return m_upperTranslation; + } + /** + * Set the joint limits, usually in meters. + */ + public function SetLimits(lower:Number, upper:Number) : void + { + //b2Settings.b2Assert(lower <= upper); + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_lowerTranslation = lower; + m_upperTranslation = upper; + } + /** + * Is the joint motor enabled? + */ + public function IsMotorEnabled() : Boolean + { + return m_enableMotor; + } + /** + * Enable/disable the joint motor. + */ + public function EnableMotor(flag:Boolean) : void + { + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_enableMotor = flag; + } + /** + * Set the motor speed, usually in meters per second. + */ + public function SetMotorSpeed(speed:Number) : void + { + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_motorSpeed = speed; + } + /** + * Get the motor speed, usually in meters per second. + */ + public function GetMotorSpeed() :Number + { + return m_motorSpeed; + } + + /** + * Set the maximum motor force, usually in N. + */ + public function SetMaxMotorForce(force:Number) : void + { + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_maxMotorForce = force; + } + /** + * Get the current motor force, usually in N. + */ + public function GetMotorForce() : Number + { + return m_motorImpulse; + } + + + //--------------- Internals Below ------------------- + + /** @private */ + public function b2PrismaticJoint(def:b2PrismaticJointDef){ + super(def); + + var tMat:b2Mat22; + var tX:Number; + var tY:Number; + + m_localAnchor1.SetV(def.localAnchorA); + m_localAnchor2.SetV(def.localAnchorB); + m_localXAxis1.SetV(def.localAxisA); + + //m_localYAxisA = b2Cross(1.0f, m_localXAxisA); + m_localYAxis1.x = -m_localXAxis1.y; + m_localYAxis1.y = m_localXAxis1.x; + + m_refAngle = def.referenceAngle; + + m_impulse.SetZero(); + m_motorMass = 0.0; + m_motorImpulse = 0.0; + + m_lowerTranslation = def.lowerTranslation; + m_upperTranslation = def.upperTranslation; + m_maxMotorForce = def.maxMotorForce; + m_motorSpeed = def.motorSpeed; + m_enableLimit = def.enableLimit; + m_enableMotor = def.enableMotor; + m_limitState = e_inactiveLimit; + + m_axis.SetZero(); + m_perp.SetZero(); + } + + b2internal override function InitVelocityConstraints(step:b2TimeStep) : void{ + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + var tX:Number; + + m_localCenterA.SetV(bA.GetLocalCenter()); + m_localCenterB.SetV(bB.GetLocalCenter()); + + var xf1:b2Transform = bA.GetTransform(); + var xf2:b2Transform = bB.GetTransform(); + + // Compute the effective masses. + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - m_localCenterA.x; + var r1Y:Number = m_localAnchor1.y - m_localCenterA.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - m_localCenterB.x; + var r2Y:Number = m_localAnchor2.y - m_localCenterB.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + //b2Vec2 d = bB->m_sweep.c + r2 - bA->m_sweep.c - r1; + var dX:Number = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X; + var dY:Number = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y; + + m_invMassA = bA.m_invMass; + m_invMassB = bB.m_invMass; + m_invIA = bA.m_invI; + m_invIB = bB.m_invI; + + // Compute motor Jacobian and effective mass. + { + m_axis.SetV(b2Math.MulMV(xf1.R, m_localXAxis1)); + //m_a1 = b2Math.b2Cross(d + r1, m_axis); + m_a1 = (dX + r1X) * m_axis.y - (dY + r1Y) * m_axis.x; + //m_a2 = b2Math.b2Cross(r2, m_axis); + m_a2 = r2X * m_axis.y - r2Y * m_axis.x; + + m_motorMass = m_invMassA + m_invMassB + m_invIA * m_a1 * m_a1 + m_invIB * m_a2 * m_a2; + if(m_motorMass > Number.MIN_VALUE) + m_motorMass = 1.0 / m_motorMass; + } + + // Prismatic constraint. + { + m_perp.SetV(b2Math.MulMV(xf1.R, m_localYAxis1)); + //m_s1 = b2Math.b2Cross(d + r1, m_perp); + m_s1 = (dX + r1X) * m_perp.y - (dY + r1Y) * m_perp.x; + //m_s2 = b2Math.b2Cross(r2, m_perp); + m_s2 = r2X * m_perp.y - r2Y * m_perp.x; + + var m1:Number = m_invMassA; + var m2:Number = m_invMassB; + var i1:Number = m_invIA; + var i2:Number = m_invIB; + + m_K.col1.x = m1 + m2 + i1 * m_s1 * m_s1 + i2 * m_s2 * m_s2; + m_K.col1.y = i1 * m_s1 + i2 * m_s2; + m_K.col1.z = i1 * m_s1 * m_a1 + i2 * m_s2 * m_a2; + m_K.col2.x = m_K.col1.y; + m_K.col2.y = i1 + i2; + m_K.col2.z = i1 * m_a1 + i2 * m_a2; + m_K.col3.x = m_K.col1.z; + m_K.col3.y = m_K.col2.z; + m_K.col3.z = m1 + m2 + i1 * m_a1 * m_a1 + i2 * m_a2 * m_a2; + } + + // Compute motor and limit terms + if (m_enableLimit) + { + //float32 jointTranslation = b2Dot(m_axis, d); + var jointTransition:Number = m_axis.x * dX + m_axis.y * dY; + if (b2Math.Abs(m_upperTranslation - m_lowerTranslation) < 2.0 * b2Settings.b2_linearSlop) + { + m_limitState = e_equalLimits; + } + else if (jointTransition <= m_lowerTranslation) + { + if (m_limitState != e_atLowerLimit) + { + m_limitState = e_atLowerLimit; + m_impulse.z = 0.0; + } + } + else if (jointTransition >= m_upperTranslation) + { + if (m_limitState != e_atUpperLimit) + { + m_limitState = e_atUpperLimit; + m_impulse.z = 0.0; + } + } + else + { + m_limitState = e_inactiveLimit; + m_impulse.z = 0.0; + } + } + else + { + m_limitState = e_inactiveLimit; + } + + if (m_enableMotor == false) + { + m_motorImpulse = 0.0 + } + + if (step.warmStarting) + { + // Account for variable time step. + m_impulse.x *= step.dtRatio; + m_impulse.y *= step.dtRatio; + m_motorImpulse *= step.dtRatio; + + //b2Vec2 P = m_impulse.x * m_perp + (m_motorImpulse + m_impulse.z) * m_axis; + var PX:Number = m_impulse.x * m_perp.x + (m_motorImpulse + m_impulse.z) * m_axis.x; + var PY:Number = m_impulse.x * m_perp.y + (m_motorImpulse + m_impulse.z) * m_axis.y; + var L1:Number = m_impulse.x * m_s1 + m_impulse.y + (m_motorImpulse + m_impulse.z) * m_a1; + var L2:Number = m_impulse.x * m_s2 + m_impulse.y + (m_motorImpulse + m_impulse.z) * m_a2; + + //bA->m_linearVelocity -= m_invMassA * P; + bA.m_linearVelocity.x -= m_invMassA * PX; + bA.m_linearVelocity.y -= m_invMassA * PY; + //bA->m_angularVelocity -= m_invIA * L1; + bA.m_angularVelocity -= m_invIA * L1; + + //bB->m_linearVelocity += m_invMassB * P; + bB.m_linearVelocity.x += m_invMassB * PX; + bB.m_linearVelocity.y += m_invMassB * PY; + //bB->m_angularVelocity += m_invIB * L2; + bB.m_angularVelocity += m_invIB * L2; + } + else + { + m_impulse.SetZero(); + m_motorImpulse = 0.0; + } + } + + b2internal override function SolveVelocityConstraints(step:b2TimeStep) : void{ + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var v1:b2Vec2 = bA.m_linearVelocity; + var w1:Number = bA.m_angularVelocity; + var v2:b2Vec2 = bB.m_linearVelocity; + var w2:Number = bB.m_angularVelocity; + + var PX:Number; + var PY:Number; + var L1:Number; + var L2:Number; + + // Solve linear motor constraint + if (m_enableMotor && m_limitState != e_equalLimits) + { + //float32 Cdot = b2Dot(m_axis, v2 - v1) + m_a2 * w2 - m_a1 * w1; + var Cdot:Number = m_axis.x * (v2.x -v1.x) + m_axis.y * (v2.y - v1.y) + m_a2 * w2 - m_a1 * w1; + var impulse:Number = m_motorMass * (m_motorSpeed - Cdot); + var oldImpulse:Number = m_motorImpulse; + var maxImpulse:Number = step.dt * m_maxMotorForce; + m_motorImpulse = b2Math.Clamp(m_motorImpulse + impulse, -maxImpulse, maxImpulse); + impulse = m_motorImpulse - oldImpulse; + + PX = impulse * m_axis.x; + PY = impulse * m_axis.y; + L1 = impulse * m_a1; + L2 = impulse * m_a2; + + v1.x -= m_invMassA * PX; + v1.y -= m_invMassA * PY; + w1 -= m_invIA * L1; + + v2.x += m_invMassB * PX; + v2.y += m_invMassB * PY; + w2 += m_invIB * L2; + } + + //Cdot1.x = b2Dot(m_perp, v2 - v1) + m_s2 * w2 - m_s1 * w1; + var Cdot1X:Number = m_perp.x * (v2.x - v1.x) + m_perp.y * (v2.y - v1.y) + m_s2 * w2 - m_s1 * w1; + var Cdot1Y:Number = w2 - w1; + + if (m_enableLimit && m_limitState != e_inactiveLimit) + { + // Solve prismatic and limit constraint in block form + //Cdot2 = b2Dot(m_axis, v2 - v1) + m_a2 * w2 - m_a1 * w1; + var Cdot2:Number = m_axis.x * (v2.x - v1.x) + m_axis.y * (v2.y - v1.y) + m_a2 * w2 - m_a1 * w1; + + var f1:b2Vec3 = m_impulse.Copy(); + var df:b2Vec3 = m_K.Solve33(new b2Vec3(), -Cdot1X, -Cdot1Y, -Cdot2); + + m_impulse.Add(df); + + if (m_limitState == e_atLowerLimit) + { + m_impulse.z = b2Math.Max(m_impulse.z, 0.0); + } + else if (m_limitState == e_atUpperLimit) + { + m_impulse.z = b2Math.Min(m_impulse.z, 0.0); + } + + // f2(1:2) = invK(1:2,1:2) * (-Cdot3\(1:2) - K(1:2,3) * (f2(3) - f1(3))) + f1(1:2) + //b2Vec2 b = -Cdot1 - (m_impulse.z - f1.z) * b2Vec2(m_K.col3.x, m_K.col3.y); + var bX:Number = -Cdot1X - (m_impulse.z - f1.z) * m_K.col3.x; + var bY:Number = -Cdot1Y - (m_impulse.z - f1.z) * m_K.col3.y; + var f2r:b2Vec2 = m_K.Solve22(new b2Vec2(), bX, bY) + f2r.x += f1.x; + f2r.y += f1.y; + m_impulse.x = f2r.x; + m_impulse.y = f2r.y; + + df.x = m_impulse.x - f1.x; + df.y = m_impulse.y - f1.y; + df.z = m_impulse.z - f1.z; + + PX = df.x * m_perp.x + df.z * m_axis.x; + PY = df.x * m_perp.y + df.z * m_axis.y; + L1 = df.x * m_s1 + df.y + df.z * m_a1; + L2 = df.x * m_s2 + df.y + df.z * m_a2; + + v1.x -= m_invMassA * PX; + v1.y -= m_invMassA * PY; + w1 -= m_invIA * L1; + + v2.x += m_invMassB * PX; + v2.y += m_invMassB * PY; + w2 += m_invIB * L2; + } + else + { + // Limit is inactive, just solve the prismatic constraint in block form. + var df2:b2Vec2 = m_K.Solve22(new b2Vec2(), -Cdot1X, -Cdot1Y); + m_impulse.x += df2.x; + m_impulse.y += df2.y; + + PX = df2.x * m_perp.x; + PY = df2.x * m_perp.y; + L1 = df2.x * m_s1 + df2.y; + L2 = df2.x * m_s2 + df2.y; + + v1.x -= m_invMassA * PX; + v1.y -= m_invMassA * PY; + w1 -= m_invIA * L1; + + v2.x += m_invMassB * PX; + v2.y += m_invMassB * PY; + w2 += m_invIB * L2; + } + + bA.m_linearVelocity.SetV(v1); + bA.m_angularVelocity = w1; + bB.m_linearVelocity.SetV(v2); + bB.m_angularVelocity = w2; + } + + b2internal override function SolvePositionConstraints(baumgarte:Number ):Boolean + { + //B2_NOT_USED(baumgarte); + + + var limitC:Number; + var oldLimitImpulse:Number; + + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var c1:b2Vec2 = bA.m_sweep.c; + var a1:Number = bA.m_sweep.a; + + var c2:b2Vec2 = bB.m_sweep.c; + var a2:Number = bB.m_sweep.a; + + var tMat:b2Mat22; + var tX:Number; + + var m1:Number; + var m2:Number; + var i1:Number; + var i2:Number; + + // Solve linear limit constraint + var linearError:Number = 0.0; + var angularError:Number = 0.0; + var active:Boolean = false; + var C2:Number = 0.0; + + var R1:b2Mat22 = b2Mat22.FromAngle(a1); + var R2:b2Mat22 = b2Mat22.FromAngle(a2); + + //b2Vec2 r1 = b2Mul(R1, m_localAnchor1 - m_localCenterA); + tMat = R1; + var r1X:Number = m_localAnchor1.x - m_localCenterA.x; + var r1Y:Number = m_localAnchor1.y - m_localCenterA.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(R2, m_localAnchor2 - m_localCenterB); + tMat = R2; + var r2X:Number = m_localAnchor2.x - m_localCenterB.x; + var r2Y:Number = m_localAnchor2.y - m_localCenterB.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + var dX:Number = c2.x + r2X - c1.x - r1X; + var dY:Number = c2.y + r2Y - c1.y - r1Y; + + if (m_enableLimit) + { + m_axis = b2Math.MulMV(R1, m_localXAxis1); + + //m_a1 = b2Math.b2Cross(d + r1, m_axis); + m_a1 = (dX + r1X) * m_axis.y - (dY + r1Y) * m_axis.x; + //m_a2 = b2Math.b2Cross(r2, m_axis); + m_a2 = r2X * m_axis.y - r2Y * m_axis.x; + + var translation:Number = m_axis.x * dX + m_axis.y * dY; + if (b2Math.Abs(m_upperTranslation - m_lowerTranslation) < 2.0 * b2Settings.b2_linearSlop) + { + // Prevent large angular corrections. + C2 = b2Math.Clamp(translation, -b2Settings.b2_maxLinearCorrection, b2Settings.b2_maxLinearCorrection); + linearError = b2Math.Abs(translation); + active = true; + } + else if (translation <= m_lowerTranslation) + { + // Prevent large angular corrections and allow some slop. + C2 = b2Math.Clamp(translation - m_lowerTranslation + b2Settings.b2_linearSlop, -b2Settings.b2_maxLinearCorrection, 0.0); + linearError = m_lowerTranslation - translation; + active = true; + } + else if (translation >= m_upperTranslation) + { + // Prevent large angular corrections and allow some slop. + C2 = b2Math.Clamp(translation - m_upperTranslation + b2Settings.b2_linearSlop, 0.0, b2Settings.b2_maxLinearCorrection); + linearError = translation - m_upperTranslation; + active = true; + } + } + + m_perp = b2Math.MulMV(R1, m_localYAxis1); + + //m_s1 = b2Cross(d + r1, m_perp); + m_s1 = (dX + r1X) * m_perp.y - (dY + r1Y) * m_perp.x; + //m_s2 = b2Cross(r2, m_perp); + m_s2 = r2X * m_perp.y - r2Y * m_perp.x; + + var impulse:b2Vec3 = new b2Vec3(); + var C1X:Number = m_perp.x * dX + m_perp.y * dY; + var C1Y:Number = a2 - a1 - m_refAngle; + + linearError = b2Math.Max(linearError, b2Math.Abs(C1X)); + angularError = b2Math.Abs(C1Y); + + if (active) + { + m1 = m_invMassA; + m2 = m_invMassB; + i1 = m_invIA; + i2 = m_invIB; + + m_K.col1.x = m1 + m2 + i1 * m_s1 * m_s1 + i2 * m_s2 * m_s2; + m_K.col1.y = i1 * m_s1 + i2 * m_s2; + m_K.col1.z = i1 * m_s1 * m_a1 + i2 * m_s2 * m_a2; + m_K.col2.x = m_K.col1.y; + m_K.col2.y = i1 + i2; + m_K.col2.z = i1 * m_a1 + i2 * m_a2; + m_K.col3.x = m_K.col1.z; + m_K.col3.y = m_K.col2.z; + m_K.col3.z = m1 + m2 + i1 * m_a1 * m_a1 + i2 * m_a2 * m_a2; + + m_K.Solve33(impulse, -C1X, -C1Y, -C2); + } + else + { + m1 = m_invMassA; + m2 = m_invMassB; + i1 = m_invIA; + i2 = m_invIB; + + var k11:Number = m1 + m2 + i1 * m_s1 * m_s1 + i2 * m_s2 * m_s2; + var k12:Number = i1 * m_s1 + i2 * m_s2; + var k22:Number = i1 + i2; + + m_K.col1.Set(k11, k12, 0.0); + m_K.col2.Set(k12, k22, 0.0); + + var impulse1:b2Vec2 = m_K.Solve22(new b2Vec2(), -C1X, -C1Y); + impulse.x = impulse1.x; + impulse.y = impulse1.y; + impulse.z = 0.0; + } + + var PX:Number = impulse.x * m_perp.x + impulse.z * m_axis.x; + var PY:Number = impulse.x * m_perp.y + impulse.z * m_axis.y; + var L1:Number = impulse.x * m_s1 + impulse.y + impulse.z * m_a1; + var L2:Number = impulse.x * m_s2 + impulse.y + impulse.z * m_a2; + + c1.x -= m_invMassA * PX; + c1.y -= m_invMassA * PY; + a1 -= m_invIA * L1; + + c2.x += m_invMassB * PX; + c2.y += m_invMassB * PY; + a2 += m_invIB * L2; + + // TODO_ERIN remove need for this + //bA.m_sweep.c = c1; //Already done by reference + bA.m_sweep.a = a1; + //bB.m_sweep.c = c2; //Already done by reference + bB.m_sweep.a = a2; + bA.SynchronizeTransform(); + bB.SynchronizeTransform(); + + return linearError <= b2Settings.b2_linearSlop && angularError <= b2Settings.b2_angularSlop; + + } + + b2internal var m_localAnchor1:b2Vec2 = new b2Vec2(); + b2internal var m_localAnchor2:b2Vec2 = new b2Vec2(); + b2internal var m_localXAxis1:b2Vec2 = new b2Vec2(); + private var m_localYAxis1:b2Vec2 = new b2Vec2(); + private var m_refAngle:Number; + + private var m_axis:b2Vec2 = new b2Vec2(); + private var m_perp:b2Vec2 = new b2Vec2(); + private var m_s1:Number; + private var m_s2:Number; + private var m_a1:Number; + private var m_a2:Number; + + private var m_K:b2Mat33 = new b2Mat33(); + private var m_impulse:b2Vec3 = new b2Vec3(); + + private var m_motorMass:Number; // effective mass for motor/limit translational constraint. + private var m_motorImpulse:Number; + + private var m_lowerTranslation:Number; + private var m_upperTranslation:Number; + private var m_maxMotorForce:Number; + private var m_motorSpeed:Number; + + private var m_enableLimit:Boolean; + private var m_enableMotor:Boolean; + private var m_limitState:int; +}; + +} diff --git a/srclib/Box2D/Dynamics/Joints/b2PrismaticJointDef.as b/srclib/Box2D/Dynamics/Joints/b2PrismaticJointDef.as new file mode 100644 index 00000000..4d6820a0 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2PrismaticJointDef.as @@ -0,0 +1,116 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints { + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + import Box2D.Dynamics.*; + + +use namespace b2internal; + + +/** +* Prismatic joint definition. This requires defining a line of +* motion using an axis and an anchor point. The definition uses local +* anchor points and a local axis so that the initial configuration +* can violate the constraint slightly. The joint translation is zero +* when the local anchor points coincide in world space. Using local +* anchors and a local axis helps when saving and loading a game. +* @see b2PrismaticJoint +*/ +public class b2PrismaticJointDef extends b2JointDef +{ + public function b2PrismaticJointDef() + { + type = b2Joint.e_prismaticJoint; + //localAnchor1.SetZero(); + //localAnchor2.SetZero(); + localAxisA.Set(1.0, 0.0); + referenceAngle = 0.0; + enableLimit = false; + lowerTranslation = 0.0; + upperTranslation = 0.0; + enableMotor = false; + maxMotorForce = 0.0; + motorSpeed = 0.0; + } + + public function Initialize(bA:b2Body, bB:b2Body, anchor:b2Vec2, axis:b2Vec2) : void + { + bodyA = bA; + bodyB = bB; + localAnchorA = bodyA.GetLocalPoint(anchor); + localAnchorB = bodyB.GetLocalPoint(anchor); + localAxisA = bodyA.GetLocalVector(axis); + referenceAngle = bodyB.GetAngle() - bodyA.GetAngle(); + } + + /** + * The local anchor point relative to bodyA's origin. + */ + public var localAnchorA:b2Vec2 = new b2Vec2(); + + /** + * The local anchor point relative to bodyB's origin. + */ + public var localAnchorB:b2Vec2 = new b2Vec2(); + + /** + * The local translation axis in body1. + */ + public var localAxisA:b2Vec2 = new b2Vec2(); + + /** + * The constrained angle between the bodies: bodyB_angle - bodyA_angle. + */ + public var referenceAngle:Number; + + /** + * Enable/disable the joint limit. + */ + public var enableLimit:Boolean; + + /** + * The lower translation limit, usually in meters. + */ + public var lowerTranslation:Number; + + /** + * The upper translation limit, usually in meters. + */ + public var upperTranslation:Number; + + /** + * Enable/disable the joint motor. + */ + public var enableMotor:Boolean; + + /** + * The maximum motor torque, usually in N-m. + */ + public var maxMotorForce:Number; + + /** + * The desired motor speed in radians per second. + */ + public var motorSpeed:Number; +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2PulleyJoint.as b/srclib/Box2D/Dynamics/Joints/b2PulleyJoint.as new file mode 100644 index 00000000..b5b61181 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2PulleyJoint.as @@ -0,0 +1,668 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints { + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; + + + +use namespace b2internal; + + +/** +* The pulley joint is connected to two bodies and two fixed ground points. +* The pulley supports a ratio such that: +* length1 + ratio * length2 <= constant +* Yes, the force transmitted is scaled by the ratio. +* The pulley also enforces a maximum length limit on both sides. This is +* useful to prevent one side of the pulley hitting the top. +* @see b2PulleyJointDef +*/ +public class b2PulleyJoint extends b2Joint +{ + /** @inheritDoc */ + public override function GetAnchorA():b2Vec2{ + return m_bodyA.GetWorldPoint(m_localAnchor1); + } + /** @inheritDoc */ + public override function GetAnchorB():b2Vec2{ + return m_bodyB.GetWorldPoint(m_localAnchor2); + } + + /** @inheritDoc */ + public override function GetReactionForce(inv_dt:Number) :b2Vec2 + { + //b2Vec2 P = m_impulse * m_u2; + //return inv_dt * P; + return new b2Vec2(inv_dt * m_impulse * m_u2.x, inv_dt * m_impulse * m_u2.y); + } + + /** @inheritDoc */ + public override function GetReactionTorque(inv_dt:Number) :Number + { + //B2_NOT_USED(inv_dt); + return 0.0; + } + + /** + * Get the first ground anchor. + */ + public function GetGroundAnchorA() :b2Vec2 + { + //return m_ground.m_xf.position + m_groundAnchor1; + var a:b2Vec2 = m_ground.m_xf.position.Copy(); + a.Add(m_groundAnchor1); + return a; + } + + /** + * Get the second ground anchor. + */ + public function GetGroundAnchorB() :b2Vec2 + { + //return m_ground.m_xf.position + m_groundAnchor2; + var a:b2Vec2 = m_ground.m_xf.position.Copy(); + a.Add(m_groundAnchor2); + return a; + } + + /** + * Get the current length of the segment attached to body1. + */ + public function GetLength1() :Number + { + var p:b2Vec2 = m_bodyA.GetWorldPoint(m_localAnchor1); + //b2Vec2 s = m_ground->m_xf.position + m_groundAnchor1; + var sX:Number = m_ground.m_xf.position.x + m_groundAnchor1.x; + var sY:Number = m_ground.m_xf.position.y + m_groundAnchor1.y; + //b2Vec2 d = p - s; + var dX:Number = p.x - sX; + var dY:Number = p.y - sY; + //return d.Length(); + return Math.sqrt(dX*dX + dY*dY); + } + + /** + * Get the current length of the segment attached to body2. + */ + public function GetLength2() :Number + { + var p:b2Vec2 = m_bodyB.GetWorldPoint(m_localAnchor2); + //b2Vec2 s = m_ground->m_xf.position + m_groundAnchor2; + var sX:Number = m_ground.m_xf.position.x + m_groundAnchor2.x; + var sY:Number = m_ground.m_xf.position.y + m_groundAnchor2.y; + //b2Vec2 d = p - s; + var dX:Number = p.x - sX; + var dY:Number = p.y - sY; + //return d.Length(); + return Math.sqrt(dX*dX + dY*dY); + } + + /** + * Get the pulley ratio. + */ + public function GetRatio():Number{ + return m_ratio; + } + + //--------------- Internals Below ------------------- + + /** @private */ + public function b2PulleyJoint(def:b2PulleyJointDef){ + + // parent + super(def); + + var tMat:b2Mat22; + var tX:Number; + var tY:Number; + + m_ground = m_bodyA.m_world.m_groundBody; + //m_groundAnchor1 = def->groundAnchorA - m_ground->m_xf.position; + m_groundAnchor1.x = def.groundAnchorA.x - m_ground.m_xf.position.x; + m_groundAnchor1.y = def.groundAnchorA.y - m_ground.m_xf.position.y; + //m_groundAnchor2 = def->groundAnchorB - m_ground->m_xf.position; + m_groundAnchor2.x = def.groundAnchorB.x - m_ground.m_xf.position.x; + m_groundAnchor2.y = def.groundAnchorB.y - m_ground.m_xf.position.y; + //m_localAnchor1 = def->localAnchorA; + m_localAnchor1.SetV(def.localAnchorA); + //m_localAnchor2 = def->localAnchorB; + m_localAnchor2.SetV(def.localAnchorB); + + //b2Settings.b2Assert(def.ratio != 0.0); + m_ratio = def.ratio; + + m_constant = def.lengthA + m_ratio * def.lengthB; + + m_maxLength1 = b2Math.Min(def.maxLengthA, m_constant - m_ratio * b2_minPulleyLength); + m_maxLength2 = b2Math.Min(def.maxLengthB, (m_constant - b2_minPulleyLength) / m_ratio); + + m_impulse = 0.0; + m_limitImpulse1 = 0.0; + m_limitImpulse2 = 0.0; + + } + + b2internal override function InitVelocityConstraints(step:b2TimeStep) : void{ + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - bA.m_sweep.localCenter.x; + var r1Y:Number = m_localAnchor1.y - bA.m_sweep.localCenter.y; + var tX:Number = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - bB.m_sweep.localCenter.x; + var r2Y:Number = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + //b2Vec2 p1 = bA->m_sweep.c + r1; + var p1X:Number = bA.m_sweep.c.x + r1X; + var p1Y:Number = bA.m_sweep.c.y + r1Y; + //b2Vec2 p2 = bB->m_sweep.c + r2; + var p2X:Number = bB.m_sweep.c.x + r2X; + var p2Y:Number = bB.m_sweep.c.y + r2Y; + + //b2Vec2 s1 = m_ground->m_xf.position + m_groundAnchor1; + var s1X:Number = m_ground.m_xf.position.x + m_groundAnchor1.x; + var s1Y:Number = m_ground.m_xf.position.y + m_groundAnchor1.y; + //b2Vec2 s2 = m_ground->m_xf.position + m_groundAnchor2; + var s2X:Number = m_ground.m_xf.position.x + m_groundAnchor2.x; + var s2Y:Number = m_ground.m_xf.position.y + m_groundAnchor2.y; + + // Get the pulley axes. + //m_u1 = p1 - s1; + m_u1.Set(p1X - s1X, p1Y - s1Y); + //m_u2 = p2 - s2; + m_u2.Set(p2X - s2X, p2Y - s2Y); + + var length1:Number = m_u1.Length(); + var length2:Number = m_u2.Length(); + + if (length1 > b2Settings.b2_linearSlop) + { + //m_u1 *= 1.0f / length1; + m_u1.Multiply(1.0 / length1); + } + else + { + m_u1.SetZero(); + } + + if (length2 > b2Settings.b2_linearSlop) + { + //m_u2 *= 1.0f / length2; + m_u2.Multiply(1.0 / length2); + } + else + { + m_u2.SetZero(); + } + + var C:Number = m_constant - length1 - m_ratio * length2; + if (C > 0.0) + { + m_state = e_inactiveLimit; + m_impulse = 0.0; + } + else + { + m_state = e_atUpperLimit; + } + + if (length1 < m_maxLength1) + { + m_limitState1 = e_inactiveLimit; + m_limitImpulse1 = 0.0; + } + else + { + m_limitState1 = e_atUpperLimit; + } + + if (length2 < m_maxLength2) + { + m_limitState2 = e_inactiveLimit; + m_limitImpulse2 = 0.0; + } + else + { + m_limitState2 = e_atUpperLimit; + } + + // Compute effective mass. + //var cr1u1:Number = b2Cross(r1, m_u1); + var cr1u1:Number = r1X * m_u1.y - r1Y * m_u1.x; + //var cr2u2:Number = b2Cross(r2, m_u2); + var cr2u2:Number = r2X * m_u2.y - r2Y * m_u2.x; + + m_limitMass1 = bA.m_invMass + bA.m_invI * cr1u1 * cr1u1; + m_limitMass2 = bB.m_invMass + bB.m_invI * cr2u2 * cr2u2; + m_pulleyMass = m_limitMass1 + m_ratio * m_ratio * m_limitMass2; + //b2Settings.b2Assert(m_limitMass1 > Number.MIN_VALUE); + //b2Settings.b2Assert(m_limitMass2 > Number.MIN_VALUE); + //b2Settings.b2Assert(m_pulleyMass > Number.MIN_VALUE); + m_limitMass1 = 1.0 / m_limitMass1; + m_limitMass2 = 1.0 / m_limitMass2; + m_pulleyMass = 1.0 / m_pulleyMass; + + if (step.warmStarting) + { + // Scale impulses to support variable time steps. + m_impulse *= step.dtRatio; + m_limitImpulse1 *= step.dtRatio; + m_limitImpulse2 *= step.dtRatio; + + // Warm starting. + //b2Vec2 P1 = (-m_impulse - m_limitImpulse1) * m_u1; + var P1X:Number = (-m_impulse - m_limitImpulse1) * m_u1.x; + var P1Y:Number = (-m_impulse - m_limitImpulse1) * m_u1.y; + //b2Vec2 P2 = (-m_ratio * m_impulse - m_limitImpulse2) * m_u2; + var P2X:Number = (-m_ratio * m_impulse - m_limitImpulse2) * m_u2.x; + var P2Y:Number = (-m_ratio * m_impulse - m_limitImpulse2) * m_u2.y; + //bA.m_linearVelocity += bA.m_invMass * P1; + bA.m_linearVelocity.x += bA.m_invMass * P1X; + bA.m_linearVelocity.y += bA.m_invMass * P1Y; + //bA.m_angularVelocity += bA.m_invI * b2Cross(r1, P1); + bA.m_angularVelocity += bA.m_invI * (r1X * P1Y - r1Y * P1X); + //bB.m_linearVelocity += bB.m_invMass * P2; + bB.m_linearVelocity.x += bB.m_invMass * P2X; + bB.m_linearVelocity.y += bB.m_invMass * P2Y; + //bB.m_angularVelocity += bB.m_invI * b2Cross(r2, P2); + bB.m_angularVelocity += bB.m_invI * (r2X * P2Y - r2Y * P2X); + } + else + { + m_impulse = 0.0; + m_limitImpulse1 = 0.0; + m_limitImpulse2 = 0.0; + } + } + + b2internal override function SolveVelocityConstraints(step:b2TimeStep) : void + { + //B2_NOT_USED(step) + + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - bA.m_sweep.localCenter.x; + var r1Y:Number = m_localAnchor1.y - bA.m_sweep.localCenter.y; + var tX:Number = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - bB.m_sweep.localCenter.x; + var r2Y:Number = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + // temp vars + var v1X:Number; + var v1Y:Number; + var v2X:Number; + var v2Y:Number; + var P1X:Number; + var P1Y:Number; + var P2X:Number; + var P2Y:Number; + var Cdot:Number; + var impulse:Number; + var oldImpulse:Number; + + if (m_state == e_atUpperLimit) + { + //b2Vec2 v1 = bA->m_linearVelocity + b2Cross(bA->m_angularVelocity, r1); + v1X = bA.m_linearVelocity.x + (-bA.m_angularVelocity * r1Y); + v1Y = bA.m_linearVelocity.y + (bA.m_angularVelocity * r1X); + //b2Vec2 v2 = bB->m_linearVelocity + b2Cross(bB->m_angularVelocity, r2); + v2X = bB.m_linearVelocity.x + (-bB.m_angularVelocity * r2Y); + v2Y = bB.m_linearVelocity.y + (bB.m_angularVelocity * r2X); + + //Cdot = -b2Dot(m_u1, v1) - m_ratio * b2Dot(m_u2, v2); + Cdot = -(m_u1.x * v1X + m_u1.y * v1Y) - m_ratio * (m_u2.x * v2X + m_u2.y * v2Y); + impulse = m_pulleyMass * (-Cdot); + oldImpulse = m_impulse; + m_impulse = b2Math.Max(0.0, m_impulse + impulse); + impulse = m_impulse - oldImpulse; + + //b2Vec2 P1 = -impulse * m_u1; + P1X = -impulse * m_u1.x; + P1Y = -impulse * m_u1.y; + //b2Vec2 P2 = - m_ratio * impulse * m_u2; + P2X = -m_ratio * impulse * m_u2.x; + P2Y = -m_ratio * impulse * m_u2.y; + //bA.m_linearVelocity += bA.m_invMass * P1; + bA.m_linearVelocity.x += bA.m_invMass * P1X; + bA.m_linearVelocity.y += bA.m_invMass * P1Y; + //bA.m_angularVelocity += bA.m_invI * b2Cross(r1, P1); + bA.m_angularVelocity += bA.m_invI * (r1X * P1Y - r1Y * P1X); + //bB.m_linearVelocity += bB.m_invMass * P2; + bB.m_linearVelocity.x += bB.m_invMass * P2X; + bB.m_linearVelocity.y += bB.m_invMass * P2Y; + //bB.m_angularVelocity += bB.m_invI * b2Cross(r2, P2); + bB.m_angularVelocity += bB.m_invI * (r2X * P2Y - r2Y * P2X); + } + + if (m_limitState1 == e_atUpperLimit) + { + //b2Vec2 v1 = bA->m_linearVelocity + b2Cross(bA->m_angularVelocity, r1); + v1X = bA.m_linearVelocity.x + (-bA.m_angularVelocity * r1Y); + v1Y = bA.m_linearVelocity.y + (bA.m_angularVelocity * r1X); + + //float32 Cdot = -b2Dot(m_u1, v1); + Cdot = -(m_u1.x * v1X + m_u1.y * v1Y); + impulse = -m_limitMass1 * Cdot; + oldImpulse = m_limitImpulse1; + m_limitImpulse1 = b2Math.Max(0.0, m_limitImpulse1 + impulse); + impulse = m_limitImpulse1 - oldImpulse; + + //b2Vec2 P1 = -impulse * m_u1; + P1X = -impulse * m_u1.x; + P1Y = -impulse * m_u1.y; + //bA.m_linearVelocity += bA->m_invMass * P1; + bA.m_linearVelocity.x += bA.m_invMass * P1X; + bA.m_linearVelocity.y += bA.m_invMass * P1Y; + //bA.m_angularVelocity += bA->m_invI * b2Cross(r1, P1); + bA.m_angularVelocity += bA.m_invI * (r1X * P1Y - r1Y * P1X); + } + + if (m_limitState2 == e_atUpperLimit) + { + //b2Vec2 v2 = bB->m_linearVelocity + b2Cross(bB->m_angularVelocity, r2); + v2X = bB.m_linearVelocity.x + (-bB.m_angularVelocity * r2Y); + v2Y = bB.m_linearVelocity.y + (bB.m_angularVelocity * r2X); + + //float32 Cdot = -b2Dot(m_u2, v2); + Cdot = -(m_u2.x * v2X + m_u2.y * v2Y); + impulse = -m_limitMass2 * Cdot; + oldImpulse = m_limitImpulse2; + m_limitImpulse2 = b2Math.Max(0.0, m_limitImpulse2 + impulse); + impulse = m_limitImpulse2 - oldImpulse; + + //b2Vec2 P2 = -impulse * m_u2; + P2X = -impulse * m_u2.x; + P2Y = -impulse * m_u2.y; + //bB->m_linearVelocity += bB->m_invMass * P2; + bB.m_linearVelocity.x += bB.m_invMass * P2X; + bB.m_linearVelocity.y += bB.m_invMass * P2Y; + //bB->m_angularVelocity += bB->m_invI * b2Cross(r2, P2); + bB.m_angularVelocity += bB.m_invI * (r2X * P2Y - r2Y * P2X); + } + } + + b2internal override function SolvePositionConstraints(baumgarte:Number):Boolean + { + //B2_NOT_USED(baumgarte) + + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + + //b2Vec2 s1 = m_ground->m_xf.position + m_groundAnchor1; + var s1X:Number = m_ground.m_xf.position.x + m_groundAnchor1.x; + var s1Y:Number = m_ground.m_xf.position.y + m_groundAnchor1.y; + //b2Vec2 s2 = m_ground->m_xf.position + m_groundAnchor2; + var s2X:Number = m_ground.m_xf.position.x + m_groundAnchor2.x; + var s2Y:Number = m_ground.m_xf.position.y + m_groundAnchor2.y; + + // temp vars + var r1X:Number; + var r1Y:Number; + var r2X:Number; + var r2Y:Number; + var p1X:Number; + var p1Y:Number; + var p2X:Number; + var p2Y:Number; + var length1:Number; + var length2:Number; + var C:Number; + var impulse:Number; + var oldImpulse:Number; + var oldLimitPositionImpulse:Number; + + var tX:Number; + + var linearError:Number = 0.0; + + if (m_state == e_atUpperLimit) + { + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + r1X = m_localAnchor1.x - bA.m_sweep.localCenter.x; + r1Y = m_localAnchor1.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + r2X = m_localAnchor2.x - bB.m_sweep.localCenter.x; + r2Y = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + //b2Vec2 p1 = bA->m_sweep.c + r1; + p1X = bA.m_sweep.c.x + r1X; + p1Y = bA.m_sweep.c.y + r1Y; + //b2Vec2 p2 = bB->m_sweep.c + r2; + p2X = bB.m_sweep.c.x + r2X; + p2Y = bB.m_sweep.c.y + r2Y; + + // Get the pulley axes. + //m_u1 = p1 - s1; + m_u1.Set(p1X - s1X, p1Y - s1Y); + //m_u2 = p2 - s2; + m_u2.Set(p2X - s2X, p2Y - s2Y); + + length1 = m_u1.Length(); + length2 = m_u2.Length(); + + if (length1 > b2Settings.b2_linearSlop) + { + //m_u1 *= 1.0f / length1; + m_u1.Multiply( 1.0 / length1 ); + } + else + { + m_u1.SetZero(); + } + + if (length2 > b2Settings.b2_linearSlop) + { + //m_u2 *= 1.0f / length2; + m_u2.Multiply( 1.0 / length2 ); + } + else + { + m_u2.SetZero(); + } + + C = m_constant - length1 - m_ratio * length2; + linearError = b2Math.Max(linearError, -C); + C = b2Math.Clamp(C + b2Settings.b2_linearSlop, -b2Settings.b2_maxLinearCorrection, 0.0); + impulse = -m_pulleyMass * C; + + p1X = -impulse * m_u1.x; + p1Y = -impulse * m_u1.y; + p2X = -m_ratio * impulse * m_u2.x; + p2Y = -m_ratio * impulse * m_u2.y; + + bA.m_sweep.c.x += bA.m_invMass * p1X; + bA.m_sweep.c.y += bA.m_invMass * p1Y; + bA.m_sweep.a += bA.m_invI * (r1X * p1Y - r1Y * p1X); + bB.m_sweep.c.x += bB.m_invMass * p2X; + bB.m_sweep.c.y += bB.m_invMass * p2Y; + bB.m_sweep.a += bB.m_invI * (r2X * p2Y - r2Y * p2X); + + bA.SynchronizeTransform(); + bB.SynchronizeTransform(); + } + + if (m_limitState1 == e_atUpperLimit) + { + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + r1X = m_localAnchor1.x - bA.m_sweep.localCenter.x; + r1Y = m_localAnchor1.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 p1 = bA->m_sweep.c + r1; + p1X = bA.m_sweep.c.x + r1X; + p1Y = bA.m_sweep.c.y + r1Y; + + //m_u1 = p1 - s1; + m_u1.Set(p1X - s1X, p1Y - s1Y); + + length1 = m_u1.Length(); + + if (length1 > b2Settings.b2_linearSlop) + { + //m_u1 *= 1.0 / length1; + m_u1.x *= 1.0 / length1; + m_u1.y *= 1.0 / length1; + } + else + { + m_u1.SetZero(); + } + + C = m_maxLength1 - length1; + linearError = b2Math.Max(linearError, -C); + C = b2Math.Clamp(C + b2Settings.b2_linearSlop, -b2Settings.b2_maxLinearCorrection, 0.0); + impulse = -m_limitMass1 * C; + + //P1 = -impulse * m_u1; + p1X = -impulse * m_u1.x; + p1Y = -impulse * m_u1.y; + + bA.m_sweep.c.x += bA.m_invMass * p1X; + bA.m_sweep.c.y += bA.m_invMass * p1Y; + //bA.m_rotation += bA.m_invI * b2Cross(r1, P1); + bA.m_sweep.a += bA.m_invI * (r1X * p1Y - r1Y * p1X); + + bA.SynchronizeTransform(); + } + + if (m_limitState2 == e_atUpperLimit) + { + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + r2X = m_localAnchor2.x - bB.m_sweep.localCenter.x; + r2Y = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + //b2Vec2 p2 = bB->m_position + r2; + p2X = bB.m_sweep.c.x + r2X; + p2Y = bB.m_sweep.c.y + r2Y; + + //m_u2 = p2 - s2; + m_u2.Set(p2X - s2X, p2Y - s2Y); + + length2 = m_u2.Length(); + + if (length2 > b2Settings.b2_linearSlop) + { + //m_u2 *= 1.0 / length2; + m_u2.x *= 1.0 / length2; + m_u2.y *= 1.0 / length2; + } + else + { + m_u2.SetZero(); + } + + C = m_maxLength2 - length2; + linearError = b2Math.Max(linearError, -C); + C = b2Math.Clamp(C + b2Settings.b2_linearSlop, -b2Settings.b2_maxLinearCorrection, 0.0); + impulse = -m_limitMass2 * C; + + //P2 = -impulse * m_u2; + p2X = -impulse * m_u2.x; + p2Y = -impulse * m_u2.y; + + //bB.m_sweep.c += bB.m_invMass * P2; + bB.m_sweep.c.x += bB.m_invMass * p2X; + bB.m_sweep.c.y += bB.m_invMass * p2Y; + //bB.m_sweep.a += bB.m_invI * b2Cross(r2, P2); + bB.m_sweep.a += bB.m_invI * (r2X * p2Y - r2Y * p2X); + + bB.SynchronizeTransform(); + } + + return linearError < b2Settings.b2_linearSlop; + } + + + + private var m_ground:b2Body; + private var m_groundAnchor1:b2Vec2 = new b2Vec2(); + private var m_groundAnchor2:b2Vec2 = new b2Vec2(); + private var m_localAnchor1:b2Vec2 = new b2Vec2(); + private var m_localAnchor2:b2Vec2 = new b2Vec2(); + + private var m_u1:b2Vec2 = new b2Vec2(); + private var m_u2:b2Vec2 = new b2Vec2(); + + private var m_constant:Number; + private var m_ratio:Number; + + private var m_maxLength1:Number; + private var m_maxLength2:Number; + + // Effective masses + private var m_pulleyMass:Number; + private var m_limitMass1:Number; + private var m_limitMass2:Number; + + // Impulses for accumulation/warm starting. + private var m_impulse:Number; + private var m_limitImpulse1:Number; + private var m_limitImpulse2:Number; + + private var m_state:int; + private var m_limitState1:int; + private var m_limitState2:int; + + // static + static b2internal const b2_minPulleyLength:Number = 2.0; +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2PulleyJointDef.as b/srclib/Box2D/Dynamics/Joints/b2PulleyJointDef.as new file mode 100644 index 00000000..7f33517c --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2PulleyJointDef.as @@ -0,0 +1,131 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints { + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + import Box2D.Dynamics.*; + + +use namespace b2internal; + + + +/** +* Pulley joint definition. This requires two ground anchors, +* two dynamic body anchor points, max lengths for each side, +* and a pulley ratio. +* @see b2PulleyJoint +*/ + +public class b2PulleyJointDef extends b2JointDef +{ + public function b2PulleyJointDef() + { + type = b2Joint.e_pulleyJoint; + groundAnchorA.Set(-1.0, 1.0); + groundAnchorB.Set(1.0, 1.0); + localAnchorA.Set(-1.0, 0.0); + localAnchorB.Set(1.0, 0.0); + lengthA = 0.0; + maxLengthA = 0.0; + lengthB = 0.0; + maxLengthB = 0.0; + ratio = 1.0; + collideConnected = true; + } + + public function Initialize(bA:b2Body, bB:b2Body, + gaA:b2Vec2, gaB:b2Vec2, + anchorA:b2Vec2, anchorB:b2Vec2, + r:Number) : void + { + bodyA = bA; + bodyB = bB; + groundAnchorA.SetV( gaA ); + groundAnchorB.SetV( gaB ); + localAnchorA = bodyA.GetLocalPoint(anchorA); + localAnchorB = bodyB.GetLocalPoint(anchorB); + //b2Vec2 d1 = anchorA - gaA; + var d1X:Number = anchorA.x - gaA.x; + var d1Y:Number = anchorA.y - gaA.y; + //length1 = d1.Length(); + lengthA = Math.sqrt(d1X*d1X + d1Y*d1Y); + + //b2Vec2 d2 = anchor2 - ga2; + var d2X:Number = anchorB.x - gaB.x; + var d2Y:Number = anchorB.y - gaB.y; + //length2 = d2.Length(); + lengthB = Math.sqrt(d2X*d2X + d2Y*d2Y); + + ratio = r; + //b2Settings.b2Assert(ratio > Number.MIN_VALUE); + var C:Number = lengthA + ratio * lengthB; + maxLengthA = C - ratio * b2PulleyJoint.b2_minPulleyLength; + maxLengthB = (C - b2PulleyJoint.b2_minPulleyLength) / ratio; + } + + /** + * The first ground anchor in world coordinates. This point never moves. + */ + public var groundAnchorA:b2Vec2 = new b2Vec2(); + + /** + * The second ground anchor in world coordinates. This point never moves. + */ + public var groundAnchorB:b2Vec2 = new b2Vec2(); + + /** + * The local anchor point relative to bodyA's origin. + */ + public var localAnchorA:b2Vec2 = new b2Vec2(); + + /** + * The local anchor point relative to bodyB's origin. + */ + public var localAnchorB:b2Vec2 = new b2Vec2(); + + /** + * The a reference length for the segment attached to bodyA. + */ + public var lengthA:Number; + + /** + * The maximum length of the segment attached to bodyA. + */ + public var maxLengthA:Number; + + /** + * The a reference length for the segment attached to bodyB. + */ + public var lengthB:Number; + + /** + * The maximum length of the segment attached to bodyB. + */ + public var maxLengthB:Number; + + /** + * The pulley ratio, used to simulate a block-and-tackle. + */ + public var ratio:Number; + +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2RevoluteJoint.as b/srclib/Box2D/Dynamics/Joints/b2RevoluteJoint.as new file mode 100644 index 00000000..e8264eab --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2RevoluteJoint.as @@ -0,0 +1,651 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; +use namespace b2internal; + +// Point-to-point constraint +// C = p2 - p1 +// Cdot = v2 - v1 +// = v2 + cross(w2, r2) - v1 - cross(w1, r1) +// J = [-I -r1_skew I r2_skew ] +// Identity used: +// w k % (rx i + ry j) = w * (-ry i + rx j) + +// Motor constraint +// Cdot = w2 - w1 +// J = [0 0 -1 0 0 1] +// K = invI1 + invI2 + +/** +* A revolute joint constrains to bodies to share a common point while they +* are free to rotate about the point. The relative rotation about the shared +* point is the joint angle. You can limit the relative rotation with +* a joint limit that specifies a lower and upper angle. You can use a motor +* to drive the relative rotation about the shared point. A maximum motor torque +* is provided so that infinite forces are not generated. +* @see b2RevoluteJointDef +*/ +public class b2RevoluteJoint extends b2Joint +{ + /** @inheritDoc */ + public override function GetAnchorA() :b2Vec2{ + return m_bodyA.GetWorldPoint(m_localAnchor1); + } + /** @inheritDoc */ + public override function GetAnchorB() :b2Vec2{ + return m_bodyB.GetWorldPoint(m_localAnchor2); + } + + /** @inheritDoc */ + public override function GetReactionForce(inv_dt:Number) :b2Vec2{ + return new b2Vec2(inv_dt * m_impulse.x, inv_dt * m_impulse.y); + } + /** @inheritDoc */ + public override function GetReactionTorque(inv_dt:Number) :Number{ + return inv_dt * m_impulse.z; + } + + /** + * Get the current joint angle in radians. + */ + public function GetJointAngle() :Number{ + //b2Body* bA = m_bodyA; + //b2Body* bB = m_bodyB; + return m_bodyB.m_sweep.a - m_bodyA.m_sweep.a - m_referenceAngle; + } + + /** + * Get the current joint angle speed in radians per second. + */ + public function GetJointSpeed() :Number{ + //b2Body* bA = m_bodyA; + //b2Body* bB = m_bodyB; + return m_bodyB.m_angularVelocity - m_bodyA.m_angularVelocity; + } + + /** + * Is the joint limit enabled? + */ + public function IsLimitEnabled() :Boolean{ + return m_enableLimit; + } + + /** + * Enable/disable the joint limit. + */ + public function EnableLimit(flag:Boolean) :void{ + m_enableLimit = flag; + } + + /** + * Get the lower joint limit in radians. + */ + public function GetLowerLimit() :Number{ + return m_lowerAngle; + } + + /** + * Get the upper joint limit in radians. + */ + public function GetUpperLimit() :Number{ + return m_upperAngle; + } + + /** + * Set the joint limits in radians. + */ + public function SetLimits(lower:Number, upper:Number) : void{ + //b2Settings.b2Assert(lower <= upper); + m_lowerAngle = lower; + m_upperAngle = upper; + } + + /** + * Is the joint motor enabled? + */ + public function IsMotorEnabled() :Boolean { + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + return m_enableMotor; + } + + /** + * Enable/disable the joint motor. + */ + public function EnableMotor(flag:Boolean) :void{ + m_enableMotor = flag; + } + + /** + * Set the motor speed in radians per second. + */ + public function SetMotorSpeed(speed:Number) : void { + m_bodyA.SetAwake(true); + m_bodyB.SetAwake(true); + m_motorSpeed = speed; + } + + /** + * Get the motor speed in radians per second. + */ + public function GetMotorSpeed() :Number{ + return m_motorSpeed; + } + + /** + * Set the maximum motor torque, usually in N-m. + */ + public function SetMaxMotorTorque(torque:Number) : void{ + m_maxMotorTorque = torque; + } + + /** + * Get the current motor torque, usually in N-m. + */ + public function GetMotorTorque() :Number{ + return m_maxMotorTorque; + } + + //--------------- Internals Below ------------------- + + /** @private */ + public function b2RevoluteJoint(def:b2RevoluteJointDef){ + super(def); + + //m_localAnchor1 = def->localAnchorA; + m_localAnchor1.SetV(def.localAnchorA); + //m_localAnchor2 = def->localAnchorB; + m_localAnchor2.SetV(def.localAnchorB); + + m_referenceAngle = def.referenceAngle; + + m_impulse.SetZero(); + m_motorImpulse = 0.0; + + m_lowerAngle = def.lowerAngle; + m_upperAngle = def.upperAngle; + m_maxMotorTorque = def.maxMotorTorque; + m_motorSpeed = def.motorSpeed; + m_enableLimit = def.enableLimit; + m_enableMotor = def.enableMotor; + m_limitState = e_inactiveLimit; + } + + // internal vars + private var K:b2Mat22 = new b2Mat22(); + private var K1:b2Mat22 = new b2Mat22(); + private var K2:b2Mat22 = new b2Mat22(); + private var K3:b2Mat22 = new b2Mat22(); + b2internal override function InitVelocityConstraints(step:b2TimeStep) : void{ + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + var tX:Number; + + if (m_enableMotor || m_enableLimit) + { + // You cannot create prismatic joint between bodies that + // both have fixed rotation. + //b2Settings.b2Assert(bA.m_invI > 0.0 || bB.m_invI > 0.0); + } + + + // Compute the effective mass matrix. + + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - bA.m_sweep.localCenter.x; + var r1Y:Number = m_localAnchor1.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - bB.m_sweep.localCenter.x; + var r2Y:Number = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + // J = [-I -r1_skew I r2_skew] + // [ 0 -1 0 1] + // r_skew = [-ry; rx] + + // Matlab + // K = [ m1+r1y^2*i1+m2+r2y^2*i2, -r1y*i1*r1x-r2y*i2*r2x, -r1y*i1-r2y*i2] + // [ -r1y*i1*r1x-r2y*i2*r2x, m1+r1x^2*i1+m2+r2x^2*i2, r1x*i1+r2x*i2] + // [ -r1y*i1-r2y*i2, r1x*i1+r2x*i2, i1+i2] + + var m1:Number = bA.m_invMass; + var m2:Number = bB.m_invMass; + var i1:Number = bA.m_invI; + var i2:Number = bB.m_invI; + + m_mass.col1.x = m1 + m2 + r1Y * r1Y * i1 + r2Y * r2Y * i2; + m_mass.col2.x = -r1Y * r1X * i1 - r2Y * r2X * i2; + m_mass.col3.x = -r1Y * i1 - r2Y * i2; + m_mass.col1.y = m_mass.col2.x; + m_mass.col2.y = m1 + m2 + r1X * r1X * i1 + r2X * r2X * i2; + m_mass.col3.y = r1X * i1 + r2X * i2; + m_mass.col1.z = m_mass.col3.x; + m_mass.col2.z = m_mass.col3.y; + m_mass.col3.z = i1 + i2; + + + m_motorMass = 1.0 / (i1 + i2); + + if (m_enableMotor == false) + { + m_motorImpulse = 0.0; + } + + if (m_enableLimit) + { + //float32 jointAngle = bB->m_sweep.a - bA->m_sweep.a - m_referenceAngle; + var jointAngle:Number = bB.m_sweep.a - bA.m_sweep.a - m_referenceAngle; + if (b2Math.Abs(m_upperAngle - m_lowerAngle) < 2.0 * b2Settings.b2_angularSlop) + { + m_limitState = e_equalLimits; + } + else if (jointAngle <= m_lowerAngle) + { + if (m_limitState != e_atLowerLimit) + { + m_impulse.z = 0.0; + } + m_limitState = e_atLowerLimit; + } + else if (jointAngle >= m_upperAngle) + { + if (m_limitState != e_atUpperLimit) + { + m_impulse.z = 0.0; + } + m_limitState = e_atUpperLimit; + } + else + { + m_limitState = e_inactiveLimit; + m_impulse.z = 0.0; + } + } + else + { + m_limitState = e_inactiveLimit; + } + + // Warm starting. + if (step.warmStarting) + { + //Scale impulses to support a variable time step + m_impulse.x *= step.dtRatio; + m_impulse.y *= step.dtRatio; + m_motorImpulse *= step.dtRatio; + + var PX:Number = m_impulse.x; + var PY:Number = m_impulse.y; + + //bA->m_linearVelocity -= m1 * P; + bA.m_linearVelocity.x -= m1 * PX; + bA.m_linearVelocity.y -= m1 * PY; + //bA->m_angularVelocity -= i1 * (b2Cross(r1, P) + m_motorImpulse + m_impulse.z); + bA.m_angularVelocity -= i1 * ((r1X * PY - r1Y * PX) + m_motorImpulse + m_impulse.z); + + //bB->m_linearVelocity += m2 * P; + bB.m_linearVelocity.x += m2 * PX; + bB.m_linearVelocity.y += m2 * PY; + //bB->m_angularVelocity += i2 * (b2Cross(r2, P) + m_motorImpulse + m_impulse.z); + bB.m_angularVelocity += i2 * ((r2X * PY - r2Y * PX) + m_motorImpulse + m_impulse.z); + } + else + { + m_impulse.SetZero(); + m_motorImpulse = 0.0; + } + } + + private var impulse3:b2Vec3 = new b2Vec3(); + private var impulse2:b2Vec2 = new b2Vec2(); + private var reduced:b2Vec2 = new b2Vec2(); + b2internal override function SolveVelocityConstraints(step:b2TimeStep) : void { + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var tMat:b2Mat22; + var tX:Number; + + var newImpulse:Number; + var r1X:Number; + var r1Y:Number; + var r2X:Number; + var r2Y:Number; + + var v1:b2Vec2 = bA.m_linearVelocity; + var w1:Number = bA.m_angularVelocity; + var v2:b2Vec2 = bB.m_linearVelocity; + var w2:Number = bB.m_angularVelocity; + + var m1:Number = bA.m_invMass; + var m2:Number = bB.m_invMass; + var i1:Number = bA.m_invI; + var i2:Number = bB.m_invI; + + // Solve motor constraint. + if (m_enableMotor && m_limitState != e_equalLimits) + { + var Cdot:Number = w2 - w1 - m_motorSpeed; + var impulse:Number = m_motorMass * ( -Cdot); + var oldImpulse:Number = m_motorImpulse; + var maxImpulse:Number = step.dt * m_maxMotorTorque; + + m_motorImpulse = b2Math.Clamp(m_motorImpulse + impulse, -maxImpulse, maxImpulse); + impulse = m_motorImpulse - oldImpulse; + + w1 -= i1 * impulse; + w2 += i2 * impulse; + } + + // Solve limit constraint. + if (m_enableLimit && m_limitState != e_inactiveLimit) + { + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + r1X = m_localAnchor1.x - bA.m_sweep.localCenter.x; + r1Y = m_localAnchor1.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + r2X = m_localAnchor2.x - bB.m_sweep.localCenter.x; + r2Y = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + // Solve point-to-point constraint + //b2Vec2 Cdot1 = v2 + b2Cross(w2, r2) - v1 - b2Cross(w1, r1); + var Cdot1X:Number = v2.x + (-w2 * r2Y) - v1.x - (-w1 * r1Y); + var Cdot1Y:Number = v2.y + (w2 * r2X) - v1.y - (w1 * r1X); + var Cdot2:Number = w2 - w1; + + m_mass.Solve33(impulse3, -Cdot1X, -Cdot1Y, -Cdot2); + + if (m_limitState == e_equalLimits) + { + m_impulse.Add(impulse3); + } + else if (m_limitState == e_atLowerLimit) + { + newImpulse = m_impulse.z + impulse3.z; + if (newImpulse < 0.0) + { + m_mass.Solve22(reduced, -Cdot1X, -Cdot1Y); + impulse3.x = reduced.x; + impulse3.y = reduced.y; + impulse3.z = -m_impulse.z; + m_impulse.x += reduced.x; + m_impulse.y += reduced.y; + m_impulse.z = 0.0; + } + } + else if (m_limitState == e_atUpperLimit) + { + newImpulse = m_impulse.z + impulse3.z; + if (newImpulse > 0.0) + { + m_mass.Solve22(reduced, -Cdot1X, -Cdot1Y); + impulse3.x = reduced.x; + impulse3.y = reduced.y; + impulse3.z = -m_impulse.z; + m_impulse.x += reduced.x; + m_impulse.y += reduced.y; + m_impulse.z = 0.0; + } + } + + v1.x -= m1 * impulse3.x; + v1.y -= m1 * impulse3.y; + w1 -= i1 * (r1X * impulse3.y - r1Y * impulse3.x + impulse3.z); + + v2.x += m2 * impulse3.x; + v2.y += m2 * impulse3.y; + w2 += i2 * (r2X * impulse3.y - r2Y * impulse3.x + impulse3.z); + } + else + { + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + r1X = m_localAnchor1.x - bA.m_sweep.localCenter.x; + r1Y = m_localAnchor1.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + r2X = m_localAnchor2.x - bB.m_sweep.localCenter.x; + r2Y = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + //b2Vec2 Cdot = v2 + b2Cross(w2, r2) - v1 - b2Cross(w1, r1); + var CdotX:Number = v2.x + ( -w2 * r2Y) - v1.x - ( -w1 * r1Y); + var CdotY:Number = v2.y + (w2 * r2X) - v1.y - (w1 * r1X); + + m_mass.Solve22(impulse2, -CdotX, -CdotY); + + m_impulse.x += impulse2.x; + m_impulse.y += impulse2.y; + + v1.x -= m1 * impulse2.x; + v1.y -= m1 * impulse2.y; + //w1 -= i1 * b2Cross(r1, impulse2); + w1 -= i1 * ( r1X * impulse2.y - r1Y * impulse2.x); + + v2.x += m2 * impulse2.x; + v2.y += m2 * impulse2.y; + //w2 += i2 * b2Cross(r2, impulse2); + w2 += i2 * ( r2X * impulse2.y - r2Y * impulse2.x); + } + + bA.m_linearVelocity.SetV(v1); + bA.m_angularVelocity = w1; + bB.m_linearVelocity.SetV(v2); + bB.m_angularVelocity = w2; + } + + private static var tImpulse:b2Vec2 = new b2Vec2(); + b2internal override function SolvePositionConstraints(baumgarte:Number):Boolean{ + + // TODO_ERIN block solve with limit + + var oldLimitImpulse:Number; + var C:Number; + + var tMat:b2Mat22; + + var bA:b2Body = m_bodyA; + var bB:b2Body = m_bodyB; + + var angularError:Number = 0.0; + var positionError:Number = 0.0; + + var tX:Number; + + var impulseX:Number; + var impulseY:Number; + + // Solve angular limit constraint. + if (m_enableLimit && m_limitState != e_inactiveLimit) + { + var angle:Number = bB.m_sweep.a - bA.m_sweep.a - m_referenceAngle; + var limitImpulse:Number = 0.0; + + if (m_limitState == e_equalLimits) + { + // Prevent large angular corrections + C = b2Math.Clamp(angle - m_lowerAngle, -b2Settings.b2_maxAngularCorrection, b2Settings.b2_maxAngularCorrection); + limitImpulse = -m_motorMass * C; + angularError = b2Math.Abs(C); + } + else if (m_limitState == e_atLowerLimit) + { + C = angle - m_lowerAngle; + angularError = -C; + + // Prevent large angular corrections and allow some slop. + C = b2Math.Clamp(C + b2Settings.b2_angularSlop, -b2Settings.b2_maxAngularCorrection, 0.0); + limitImpulse = -m_motorMass * C; + } + else if (m_limitState == e_atUpperLimit) + { + C = angle - m_upperAngle; + angularError = C; + + // Prevent large angular corrections and allow some slop. + C = b2Math.Clamp(C - b2Settings.b2_angularSlop, 0.0, b2Settings.b2_maxAngularCorrection); + limitImpulse = -m_motorMass * C; + } + + bA.m_sweep.a -= bA.m_invI * limitImpulse; + bB.m_sweep.a += bB.m_invI * limitImpulse; + + bA.SynchronizeTransform(); + bB.SynchronizeTransform(); + } + + // Solve point-to-point constraint + { + //b2Vec2 r1 = b2Mul(bA->m_xf.R, m_localAnchor1 - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var r1X:Number = m_localAnchor1.x - bA.m_sweep.localCenter.x; + var r1Y:Number = m_localAnchor1.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y); + r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y); + r1X = tX; + //b2Vec2 r2 = b2Mul(bB->m_xf.R, m_localAnchor2 - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var r2X:Number = m_localAnchor2.x - bB.m_sweep.localCenter.x; + var r2Y:Number = m_localAnchor2.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y); + r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y); + r2X = tX; + + //b2Vec2 C = bB->m_sweep.c + r2 - bA->m_sweep.c - r1; + var CX:Number = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X; + var CY:Number = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y; + var CLengthSquared:Number = CX * CX + CY * CY; + var CLength:Number = Math.sqrt(CLengthSquared); + positionError = CLength; + + var invMass1:Number = bA.m_invMass; + var invMass2:Number = bB.m_invMass; + var invI1:Number = bA.m_invI; + var invI2:Number = bB.m_invI; + + //Handle large detachment. + const k_allowedStretch:Number = 10.0 * b2Settings.b2_linearSlop; + if (CLengthSquared > k_allowedStretch * k_allowedStretch) + { + // Use a particle solution (no rotation) + //b2Vec2 u = C; u.Normalize(); + var uX:Number = CX / CLength; + var uY:Number = CY / CLength; + var k:Number = invMass1 + invMass2; + //b2Settings.b2Assert(k>Number.MIN_VALUE) + var m:Number = 1.0 / k; + impulseX = m * ( -CX); + impulseY = m * ( -CY); + const k_beta:Number = 0.5; + bA.m_sweep.c.x -= k_beta * invMass1 * impulseX; + bA.m_sweep.c.y -= k_beta * invMass1 * impulseY; + bB.m_sweep.c.x += k_beta * invMass2 * impulseX; + bB.m_sweep.c.y += k_beta * invMass2 * impulseY; + + //C = bB->m_sweep.c + r2 - bA->m_sweep.c - r1; + CX = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X; + CY = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y; + } + + //b2Mat22 K1; + K1.col1.x = invMass1 + invMass2; K1.col2.x = 0.0; + K1.col1.y = 0.0; K1.col2.y = invMass1 + invMass2; + + //b2Mat22 K2; + K2.col1.x = invI1 * r1Y * r1Y; K2.col2.x = -invI1 * r1X * r1Y; + K2.col1.y = -invI1 * r1X * r1Y; K2.col2.y = invI1 * r1X * r1X; + + //b2Mat22 K3; + K3.col1.x = invI2 * r2Y * r2Y; K3.col2.x = -invI2 * r2X * r2Y; + K3.col1.y = -invI2 * r2X * r2Y; K3.col2.y = invI2 * r2X * r2X; + + //b2Mat22 K = K1 + K2 + K3; + K.SetM(K1); + K.AddM(K2); + K.AddM(K3); + //b2Vec2 impulse = K.Solve(-C); + K.Solve(tImpulse, -CX, -CY); + impulseX = tImpulse.x; + impulseY = tImpulse.y; + + //bA.m_sweep.c -= bA.m_invMass * impulse; + bA.m_sweep.c.x -= bA.m_invMass * impulseX; + bA.m_sweep.c.y -= bA.m_invMass * impulseY; + //bA.m_sweep.a -= bA.m_invI * b2Cross(r1, impulse); + bA.m_sweep.a -= bA.m_invI * (r1X * impulseY - r1Y * impulseX); + + //bB.m_sweep.c += bB.m_invMass * impulse; + bB.m_sweep.c.x += bB.m_invMass * impulseX; + bB.m_sweep.c.y += bB.m_invMass * impulseY; + //bB.m_sweep.a += bB.m_invI * b2Cross(r2, impulse); + bB.m_sweep.a += bB.m_invI * (r2X * impulseY - r2Y * impulseX); + + bA.SynchronizeTransform(); + bB.SynchronizeTransform(); + } + + return positionError <= b2Settings.b2_linearSlop && angularError <= b2Settings.b2_angularSlop; + } + + b2internal var m_localAnchor1:b2Vec2 = new b2Vec2(); // relative + b2internal var m_localAnchor2:b2Vec2 = new b2Vec2(); + private var m_impulse:b2Vec3 = new b2Vec3(); + private var m_motorImpulse:Number; + + private var m_mass:b2Mat33 = new b2Mat33(); // effective mass for point-to-point constraint. + private var m_motorMass:Number; // effective mass for motor/limit angular constraint. + private var m_enableMotor:Boolean; + private var m_maxMotorTorque:Number; + private var m_motorSpeed:Number; + + private var m_enableLimit:Boolean; + private var m_referenceAngle:Number; + private var m_lowerAngle:Number; + private var m_upperAngle:Number; + private var m_limitState:int; +}; + +} diff --git a/srclib/Box2D/Dynamics/Joints/b2RevoluteJointDef.as b/srclib/Box2D/Dynamics/Joints/b2RevoluteJointDef.as new file mode 100644 index 00000000..187e83d3 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2RevoluteJointDef.as @@ -0,0 +1,121 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints { + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + import Box2D.Dynamics.*; + + +use namespace b2internal; + + + +/** +* Revolute joint definition. This requires defining an +* anchor point where the bodies are joined. The definition +* uses local anchor points so that the initial configuration +* can violate the constraint slightly. You also need to +* specify the initial relative angle for joint limits. This +* helps when saving and loading a game. +* The local anchor points are measured from the body's origin +* rather than the center of mass because: +* 1. you might not know where the center of mass will be. +* 2. if you add/remove shapes from a body and recompute the mass, +* the joints will be broken. +* @see b2RevoluteJoint +*/ + +public class b2RevoluteJointDef extends b2JointDef +{ + public function b2RevoluteJointDef() + { + type = b2Joint.e_revoluteJoint; + localAnchorA.Set(0.0, 0.0); + localAnchorB.Set(0.0, 0.0); + referenceAngle = 0.0; + lowerAngle = 0.0; + upperAngle = 0.0; + maxMotorTorque = 0.0; + motorSpeed = 0.0; + enableLimit = false; + enableMotor = false; + } + + /** + * Initialize the bodies, anchors, and reference angle using the world + * anchor. + */ + public function Initialize(bA:b2Body, bB:b2Body, anchor:b2Vec2) : void{ + bodyA = bA; + bodyB = bB; + localAnchorA = bodyA.GetLocalPoint(anchor); + localAnchorB = bodyB.GetLocalPoint(anchor); + referenceAngle = bodyB.GetAngle() - bodyA.GetAngle(); + } + + /** + * The local anchor point relative to bodyA's origin. + */ + public var localAnchorA:b2Vec2 = new b2Vec2(); + + /** + * The local anchor point relative to bodyB's origin. + */ + public var localAnchorB:b2Vec2 = new b2Vec2(); + + /** + * The bodyB angle minus bodyA angle in the reference state (radians). + */ + public var referenceAngle:Number; + + /** + * A flag to enable joint limits. + */ + public var enableLimit:Boolean; + + /** + * The lower angle for the joint limit (radians). + */ + public var lowerAngle:Number; + + /** + * The upper angle for the joint limit (radians). + */ + public var upperAngle:Number; + + /** + * A flag to enable the joint motor. + */ + public var enableMotor:Boolean; + + /** + * The desired motor speed. Usually in radians per second. + */ + public var motorSpeed:Number; + + /** + * The maximum motor torque used to achieve the desired motor speed. + * Usually in N-m. + */ + public var maxMotorTorque:Number; + +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/Joints/b2WeldJoint.as b/srclib/Box2D/Dynamics/Joints/b2WeldJoint.as new file mode 100644 index 00000000..0eb96c86 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2WeldJoint.as @@ -0,0 +1,301 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints { + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.*; + +use namespace b2internal; + +// Point-to-point constraint +// Cdot = v2 - v1 +// = v2 + cross(w2, r2) - v1 - cross(w1, r1) +// J = [-I -r1_skew I r2_skew ] +// Identity used: +// w k % (rx i + ry j) = w * (-ry i + rx j) + +// Angle constraint +// Cdot = w2 - w1 +// J = [0 0 -1 0 0 1] +// K = invI1 + invI2 + +/** + * A weld joint essentially glues two bodies together. A weld joint may + * distort somewhat because the island constraint solver is approximate. + */ +public class b2WeldJoint extends b2Joint +{ + /** @inheritDoc */ + public override function GetAnchorA():b2Vec2{ + return m_bodyA.GetWorldPoint(m_localAnchorA); + } + /** @inheritDoc */ + public override function GetAnchorB():b2Vec2{ + return m_bodyB.GetWorldPoint(m_localAnchorB); + } + + /** @inheritDoc */ + public override function GetReactionForce(inv_dt:Number):b2Vec2 + { + return new b2Vec2(inv_dt * m_impulse.x, inv_dt * m_impulse.y); + } + + /** @inheritDoc */ + public override function GetReactionTorque(inv_dt:Number):Number + { + return inv_dt * m_impulse.z; + } + + //--------------- Internals Below ------------------- + + /** @private */ + public function b2WeldJoint(def:b2WeldJointDef){ + super(def); + + m_localAnchorA.SetV(def.localAnchorA); + m_localAnchorB.SetV(def.localAnchorB); + m_referenceAngle = def.referenceAngle; + + m_impulse.SetZero(); + m_mass = new b2Mat33(); + } + + b2internal override function InitVelocityConstraints(step:b2TimeStep) : void { + var tMat:b2Mat22; + var tX:Number; + + var bA:b2Body = m_bodyA; + var bB:b2Body= m_bodyB; + + // Compute the effective mass matrix. + //b2Vec2 rA = b2Mul(bA->m_xf.R, m_localAnchorA - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var rAX:Number = m_localAnchorA.x - bA.m_sweep.localCenter.x; + var rAY:Number = m_localAnchorA.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * rAX + tMat.col2.x * rAY); + rAY = (tMat.col1.y * rAX + tMat.col2.y * rAY); + rAX = tX; + //b2Vec2 rB = b2Mul(bB->m_xf.R, m_localAnchorB - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var rBX:Number = m_localAnchorB.x - bB.m_sweep.localCenter.x; + var rBY:Number = m_localAnchorB.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * rBX + tMat.col2.x * rBY); + rBY = (tMat.col1.y * rBX + tMat.col2.y * rBY); + rBX = tX; + + // J = [-I -r1_skew I r2_skew] + // [ 0 -1 0 1] + // r_skew = [-ry; rx] + + // Matlab + // K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x, -r1y*iA-r2y*iB] + // [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB, r1x*iA+r2x*iB] + // [ -r1y*iA-r2y*iB, r1x*iA+r2x*iB, iA+iB] + + var mA:Number = bA.m_invMass + var mB:Number = bB.m_invMass; + var iA:Number = bA.m_invI + var iB:Number = bB.m_invI; + + m_mass.col1.x = mA + mB + rAY * rAY * iA + rBY * rBY * iB; + m_mass.col2.x = -rAY * rAX * iA - rBY * rBX * iB; + m_mass.col3.x = -rAY * iA - rBY * iB; + m_mass.col1.y = m_mass.col2.x; + m_mass.col2.y = mA + mB + rAX * rAX * iA + rBX * rBX * iB; + m_mass.col3.y = rAX * iA + rBX * iB; + m_mass.col1.z = m_mass.col3.x; + m_mass.col2.z = m_mass.col3.y; + m_mass.col3.z = iA + iB; + + if (step.warmStarting) + { + // Scale impulses to support a variable time step. + m_impulse.x *= step.dtRatio; + m_impulse.y *= step.dtRatio; + m_impulse.z *= step.dtRatio; + + bA.m_linearVelocity.x -= mA * m_impulse.x; + bA.m_linearVelocity.y -= mA * m_impulse.y; + bA.m_angularVelocity -= iA * (rAX * m_impulse.y - rAY * m_impulse.x + m_impulse.z); + + bB.m_linearVelocity.x += mB * m_impulse.x; + bB.m_linearVelocity.y += mB * m_impulse.y; + bB.m_angularVelocity += iB * (rBX * m_impulse.y - rBY * m_impulse.x + m_impulse.z); + } + else + { + m_impulse.SetZero(); + } + + } + + + + b2internal override function SolveVelocityConstraints(step:b2TimeStep): void{ + //B2_NOT_USED(step); + var tMat:b2Mat22; + var tX:Number; + + var bA:b2Body = m_bodyA; + var bB:b2Body= m_bodyB; + + var vA:b2Vec2 = bA.m_linearVelocity; + var wA:Number = bA.m_angularVelocity; + var vB:b2Vec2 = bB.m_linearVelocity; + var wB:Number = bB.m_angularVelocity; + + var mA:Number = bA.m_invMass + var mB:Number = bB.m_invMass; + var iA:Number = bA.m_invI + var iB:Number = bB.m_invI; + + //b2Vec2 rA = b2Mul(bA->m_xf.R, m_localAnchorA - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var rAX:Number = m_localAnchorA.x - bA.m_sweep.localCenter.x; + var rAY:Number = m_localAnchorA.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * rAX + tMat.col2.x * rAY); + rAY = (tMat.col1.y * rAX + tMat.col2.y * rAY); + rAX = tX; + //b2Vec2 rB = b2Mul(bB->m_xf.R, m_localAnchorB - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var rBX:Number = m_localAnchorB.x - bB.m_sweep.localCenter.x; + var rBY:Number = m_localAnchorB.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * rBX + tMat.col2.x * rBY); + rBY = (tMat.col1.y * rBX + tMat.col2.y * rBY); + rBX = tX; + + + // Solve point-to-point constraint + var Cdot1X:Number = vB.x - wB * rBY - vA.x + wA * rAY; + var Cdot1Y:Number = vB.y + wB * rBX - vA.y - wA * rAX; + var Cdot2:Number = wB - wA; + var impulse:b2Vec3 = new b2Vec3(); + m_mass.Solve33(impulse, -Cdot1X, -Cdot1Y, -Cdot2); + + m_impulse.Add(impulse); + + vA.x -= mA * impulse.x; + vA.y -= mA * impulse.y; + wA -= iA * (rAX * impulse.y - rAY * impulse.x + impulse.z); + + vB.x += mB * impulse.x; + vB.y += mB * impulse.y; + wB += iB * (rBX * impulse.y - rBY * impulse.x + impulse.z); + + // References has made some sets unnecessary + //bA->m_linearVelocity = vA; + bA.m_angularVelocity = wA; + //bB->m_linearVelocity = vB; + bB.m_angularVelocity = wB; + + } + + b2internal override function SolvePositionConstraints(baumgarte:Number):Boolean + { + //B2_NOT_USED(baumgarte); + var tMat:b2Mat22; + var tX:Number; + + var bA:b2Body = m_bodyA; + var bB:b2Body= m_bodyB; + + // Compute the effective mass matrix. + //b2Vec2 rA = b2Mul(bA->m_xf.R, m_localAnchorA - bA->GetLocalCenter()); + tMat = bA.m_xf.R; + var rAX:Number = m_localAnchorA.x - bA.m_sweep.localCenter.x; + var rAY:Number = m_localAnchorA.y - bA.m_sweep.localCenter.y; + tX = (tMat.col1.x * rAX + tMat.col2.x * rAY); + rAY = (tMat.col1.y * rAX + tMat.col2.y * rAY); + rAX = tX; + //b2Vec2 rB = b2Mul(bB->m_xf.R, m_localAnchorB - bB->GetLocalCenter()); + tMat = bB.m_xf.R; + var rBX:Number = m_localAnchorB.x - bB.m_sweep.localCenter.x; + var rBY:Number = m_localAnchorB.y - bB.m_sweep.localCenter.y; + tX = (tMat.col1.x * rBX + tMat.col2.x * rBY); + rBY = (tMat.col1.y * rBX + tMat.col2.y * rBY); + rBX = tX; + + // J = [-I -r1_skew I r2_skew] + // [ 0 -1 0 1] + // r_skew = [-ry; rx] + + // Matlab + // K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x, -r1y*iA-r2y*iB] + // [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB, r1x*iA+r2x*iB] + // [ -r1y*iA-r2y*iB, r1x*iA+r2x*iB, iA+iB] + + var mA:Number = bA.m_invMass + var mB:Number = bB.m_invMass; + var iA:Number = bA.m_invI + var iB:Number = bB.m_invI; + + //b2Vec2 C1 = bB->m_sweep.c + rB - bA->m_sweep.c - rA; + var C1X:Number = bB.m_sweep.c.x + rBX - bA.m_sweep.c.x - rAX; + var C1Y:Number = bB.m_sweep.c.y + rBY - bA.m_sweep.c.y - rAY; + var C2:Number = bB.m_sweep.a - bA.m_sweep.a - m_referenceAngle; + + // Handle large detachment. + var k_allowedStretch:Number = 10.0 * b2Settings.b2_linearSlop; + var positionError:Number = Math.sqrt(C1X * C1X + C1Y * C1Y); + var angularError:Number = b2Math.Abs(C2); + if (positionError > k_allowedStretch) + { + iA *= 1.0; + iB *= 1.0; + } + + m_mass.col1.x = mA + mB + rAY * rAY * iA + rBY * rBY * iB; + m_mass.col2.x = -rAY * rAX * iA - rBY * rBX * iB; + m_mass.col3.x = -rAY * iA - rBY * iB; + m_mass.col1.y = m_mass.col2.x; + m_mass.col2.y = mA + mB + rAX * rAX * iA + rBX * rBX * iB; + m_mass.col3.y = rAX * iA + rBX * iB; + m_mass.col1.z = m_mass.col3.x; + m_mass.col2.z = m_mass.col3.y; + m_mass.col3.z = iA + iB; + + var impulse:b2Vec3 = new b2Vec3(); + m_mass.Solve33(impulse, -C1X, -C1Y, -C2); + + + bA.m_sweep.c.x -= mA * impulse.x; + bA.m_sweep.c.y -= mA * impulse.y; + bA.m_sweep.a -= iA * (rAX * impulse.y - rAY * impulse.x + impulse.z); + + bB.m_sweep.c.x += mB * impulse.x; + bB.m_sweep.c.y += mB * impulse.y; + bB.m_sweep.a += iB * (rBX * impulse.y - rBY * impulse.x + impulse.z); + + bA.SynchronizeTransform(); + bB.SynchronizeTransform(); + + return positionError <= b2Settings.b2_linearSlop && angularError <= b2Settings.b2_angularSlop; + + } + + private var m_localAnchorA:b2Vec2 = new b2Vec2(); + private var m_localAnchorB:b2Vec2 = new b2Vec2(); + private var m_referenceAngle:Number; + + private var m_impulse:b2Vec3 = new b2Vec3(); + private var m_mass:b2Mat33 = new b2Mat33(); +}; + +} diff --git a/srclib/Box2D/Dynamics/Joints/b2WeldJointDef.as b/srclib/Box2D/Dynamics/Joints/b2WeldJointDef.as new file mode 100644 index 00000000..d488ac18 --- /dev/null +++ b/srclib/Box2D/Dynamics/Joints/b2WeldJointDef.as @@ -0,0 +1,72 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics.Joints{ + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; + import Box2D.Dynamics.*; + +use namespace b2internal; + + +/** + * Weld joint definition. You need to specify local anchor points + * where they are attached and the relative body angle. The position + * of the anchor points is important for computing the reaction torque. + * @see b2WeldJoint + */ +public class b2WeldJointDef extends b2JointDef +{ + public function b2WeldJointDef() + { + type = b2Joint.e_weldJoint; + referenceAngle = 0.0; + } + + /** + * Initialize the bodies, anchors, axis, and reference angle using the world + * anchor and world axis. + */ + public function Initialize(bA:b2Body, bB:b2Body, + anchor:b2Vec2) : void + { + bodyA = bA; + bodyB = bB; + localAnchorA.SetV( bodyA.GetLocalPoint(anchor)); + localAnchorB.SetV( bodyB.GetLocalPoint(anchor)); + referenceAngle = bodyB.GetAngle() - bodyA.GetAngle(); + } + + /** + * The local anchor point relative to bodyA's origin. + */ + public var localAnchorA:b2Vec2 = new b2Vec2(); + + /** + * The local anchor point relative to bodyB's origin. + */ + public var localAnchorB:b2Vec2 = new b2Vec2(); + + /** + * The body2 angle minus body1 angle in the reference state (radians). + */ + public var referenceAngle:Number; +}; + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/b2Body.as b/srclib/Box2D/Dynamics/b2Body.as new file mode 100644 index 00000000..76ce1fcc --- /dev/null +++ b/srclib/Box2D/Dynamics/b2Body.as @@ -0,0 +1,1361 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + + import Box2D.Collision.IBroadPhase; + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.Contacts.*; + import Box2D.Dynamics.Controllers.b2ControllerEdge; + import Box2D.Dynamics.Joints.*; +use namespace b2internal; + + + +/** +* A rigid body. +*/ +public class b2Body +{ + + private function connectEdges(s1: b2EdgeShape, s2: b2EdgeShape, angle1: Number): Number + { + var angle2: Number = Math.atan2(s2.GetDirectionVector().y, s2.GetDirectionVector().x); + var coreOffset: Number = Math.tan((angle2 - angle1) * 0.5); + var core: b2Vec2 = b2Math.MulFV(coreOffset, s2.GetDirectionVector()); + core = b2Math.SubtractVV(core, s2.GetNormalVector()); + core = b2Math.MulFV(b2Settings.b2_toiSlop, core); + core = b2Math.AddVV(core, s2.GetVertex1()); + var cornerDir: b2Vec2 = b2Math.AddVV(s1.GetDirectionVector(), s2.GetDirectionVector()); + cornerDir.Normalize(); + var convex: Boolean = b2Math.Dot(s1.GetDirectionVector(), s2.GetNormalVector()) > 0.0; + s1.SetNextEdge(s2, core, cornerDir, convex); + s2.SetPrevEdge(s1, core, cornerDir, convex); + return angle2; + } + + /** + * Creates a fixture and attach it to this body. Use this function if you need + * to set some fixture parameters, like friction. Otherwise you can create the + * fixture directly from a shape. + * If the density is non-zero, this function automatically updates the mass of the body. + * Contacts are not created until the next time step. + * @param fixtureDef the fixture definition. + * @warning This function is locked during callbacks. + */ + public function CreateFixture(def:b2FixtureDef) : b2Fixture{ + //b2Settings.b2Assert(m_world.IsLocked() == false); + if (m_world.IsLocked() == true) + { + return null; + } + + // TODO: Decide on a better place to initialize edgeShapes. (b2Shape::Create() can't + // return more than one shape to add to parent body... maybe it should add + // shapes directly to the body instead of returning them?) + /* + if (def.type == b2Shape.e_edgeShape) { + var edgeDef: b2EdgeChainDef = def as b2EdgeChainDef; + var v1: b2Vec2; + var v2: b2Vec2; + var i: int; + + if (edgeDef.isALoop) { + v1 = edgeDef.vertices[edgeDef.vertexCount-1]; + i = 0; + } else { + v1 = edgeDef.vertices[0]; + i = 1; + } + + var s0: b2EdgeShape = null; + var s1: b2EdgeShape = null; + var s2: b2EdgeShape = null; + var angle: Number = 0.0; + for (; i < edgeDef.vertexCount; i++) { + v2 = edgeDef.vertices[i]; + + //void* mem = m_world->m_blockAllocator.Allocate(sizeof(b2EdgeShape)); + s2 = new b2EdgeShape(v1, v2, def); + s2.m_next = m_shapeList; + m_shapeList = s2; + ++m_shapeCount; + s2.m_body = this; + s2.CreateProxy(m_world.m_broadPhase, m_xf); + s2.UpdateSweepRadius(m_sweep.localCenter); + + if (s1 == null) { + s0 = s2; + angle = Math.atan2(s2.GetDirectionVector().y, s2.GetDirectionVector().x); + } else { + angle = connectEdges(s1, s2, angle); + } + s1 = s2; + v1 = v2; + } + if (edgeDef.isALoop) connectEdges(s1, s0, angle); + return s0; + }*/ + + var fixture:b2Fixture = new b2Fixture(); + fixture.Create(this, m_xf, def); + + if ( m_flags & e_activeFlag ) + { + var broadPhase:IBroadPhase = m_world.m_contactManager.m_broadPhase; + fixture.CreateProxy(broadPhase, m_xf); + } + + fixture.m_next = m_fixtureList; + m_fixtureList = fixture; + ++m_fixtureCount; + + fixture.m_body = this; + + // Adjust mass properties if needed + if (fixture.m_density > 0.0) + { + ResetMassData(); + } + + // Let the world know we have a new fixture. This will cause new contacts to be created + // at the beginning of the next time step. + m_world.m_flags |= b2World.e_newFixture; + + return fixture; + } + + /** + * Creates a fixture from a shape and attach it to this body. + * This is a convenience function. Use b2FixtureDef if you need to set parameters + * like friction, restitution, user data, or filtering. + * This function automatically updates the mass of the body. + * @param shape the shape to be cloned. + * @param density the shape density (set to zero for static bodies). + * @warning This function is locked during callbacks. + */ + public function CreateFixture2(shape:b2Shape, density:Number=0.0):b2Fixture + { + var def:b2FixtureDef = new b2FixtureDef(); + def.shape = shape; + def.density = density; + + return CreateFixture(def); + } + + /** + * Destroy a fixture. This removes the fixture from the broad-phase and + * destroys all contacts associated with this fixture. This will + * automatically adjust the mass of the body if the body is dynamic and the + * fixture has positive density. + * All fixtures attached to a body are implicitly destroyed when the body is destroyed. + * @param fixture the fixture to be removed. + * @warning This function is locked during callbacks. + */ + public function DestroyFixture(fixture:b2Fixture) : void{ + //b2Settings.b2Assert(m_world.IsLocked() == false); + if (m_world.IsLocked() == true) + { + return; + } + + //b2Settings.b2Assert(m_fixtureCount > 0); + //b2Fixture** node = &m_fixtureList; + var node:b2Fixture = m_fixtureList; + var ppF:b2Fixture = null; // Fix pointer-pointer stuff + var found:Boolean = false; + while (node != null) + { + if (node == fixture) + { + if (ppF) + ppF.m_next = fixture.m_next; + else + m_fixtureList = fixture.m_next; + //node = fixture.m_next; + found = true; + break; + } + + ppF = node; + node = node.m_next; + } + + // You tried to remove a shape that is not attached to this body. + //b2Settings.b2Assert(found); + + // Destroy any contacts associated with the fixture. + var edge:b2ContactEdge = m_contactList; + while (edge) + { + var c:b2Contact = edge.contact; + edge = edge.next; + + var fixtureA:b2Fixture = c.GetFixtureA(); + var fixtureB:b2Fixture = c.GetFixtureB(); + if (fixture == fixtureA || fixture == fixtureB) + { + // This destros the contact and removes it from + // this body's contact list + m_world.m_contactManager.Destroy(c); + } + } + + if ( m_flags & e_activeFlag ) + { + var broadPhase:IBroadPhase = m_world.m_contactManager.m_broadPhase; + fixture.DestroyProxy(broadPhase); + } + else + { + //b2Assert(fixture->m_proxyId == b2BroadPhase::e_nullProxy); + } + + fixture.Destroy(); + fixture.m_body = null; + fixture.m_next = null; + + --m_fixtureCount; + + // Reset the mass data. + ResetMassData(); + } + + /** + * Set the position of the body's origin and rotation (radians). + * This breaks any contacts and wakes the other bodies. + * @param position the new world position of the body's origin (not necessarily + * the center of mass). + * @param angle the new world rotation angle of the body in radians. + */ + public function SetPositionAndAngle(position:b2Vec2, angle:Number) : void{ + + var f:b2Fixture; + + //b2Settings.b2Assert(m_world.IsLocked() == false); + if (m_world.IsLocked() == true) + { + return; + } + + m_xf.R.Set(angle); + m_xf.position.SetV(position); + + //m_sweep.c0 = m_sweep.c = b2Mul(m_xf, m_sweep.localCenter); + //b2MulMV(m_xf.R, m_sweep.localCenter); + var tMat:b2Mat22 = m_xf.R; + var tVec:b2Vec2 = m_sweep.localCenter; + // (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y) + m_sweep.c.x = (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + // (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y) + m_sweep.c.y = (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //return T.position + b2Mul(T.R, v); + m_sweep.c.x += m_xf.position.x; + m_sweep.c.y += m_xf.position.y; + //m_sweep.c0 = m_sweep.c + m_sweep.c0.SetV(m_sweep.c); + + m_sweep.a0 = m_sweep.a = angle; + + var broadPhase:IBroadPhase = m_world.m_contactManager.m_broadPhase; + for (f = m_fixtureList; f; f = f.m_next) + { + f.Synchronize(broadPhase, m_xf, m_xf); + } + m_world.m_contactManager.FindNewContacts(); + } + + /** + * Set the position of the body's origin and rotation (radians). + * This breaks any contacts and wakes the other bodies. + * Note this is less efficient than the other overload - you should use that + * if the angle is available. + * @param xf the transform of position and angle to set the bdoy to. + */ + public function SetTransform(xf:b2Transform):void + { + SetPositionAndAngle(xf.position, xf.GetAngle()); + } + + /** + * Get the body transform for the body's origin. + * @return the world transform of the body's origin. + */ + public function GetTransform() : b2Transform{ + return m_xf; + } + + /** + * Get the world body origin position. + * @return the world position of the body's origin. + */ + public function GetPosition() : b2Vec2{ + return m_xf.position; + } + + /** + * Setthe world body origin position. + * @param position the new position of the body + */ + public function SetPosition(position:b2Vec2):void + { + SetPositionAndAngle(position, GetAngle()); + } + + /** + * Get the angle in radians. + * @return the current world rotation angle in radians. + */ + public function GetAngle() : Number{ + return m_sweep.a; + } + + /** + * Set the world body angle + * @param angle the new angle of the body. + */ + public function SetAngle(angle:Number) : void + { + SetPositionAndAngle(GetPosition(), angle); + } + + + /** + * Get the world position of the center of mass. + */ + public function GetWorldCenter() : b2Vec2{ + return m_sweep.c; + } + + /** + * Get the local position of the center of mass. + */ + public function GetLocalCenter() : b2Vec2{ + return m_sweep.localCenter; + } + + /** + * Set the linear velocity of the center of mass. + * @param v the new linear velocity of the center of mass. + */ + public function SetLinearVelocity(v:b2Vec2) : void { + if ( m_type == b2_staticBody ) + { + return; + } + m_linearVelocity.SetV(v); + } + + /** + * Get the linear velocity of the center of mass. + * @return the linear velocity of the center of mass. + */ + public function GetLinearVelocity() : b2Vec2{ + return m_linearVelocity; + } + + /** + * Set the angular velocity. + * @param omega the new angular velocity in radians/second. + */ + public function SetAngularVelocity(omega:Number) : void { + if ( m_type == b2_staticBody ) + { + return; + } + m_angularVelocity = omega; + } + + /** + * Get the angular velocity. + * @return the angular velocity in radians/second. + */ + public function GetAngularVelocity() : Number{ + return m_angularVelocity; + } + + /** + * Get the definition containing the body properties. + * @asonly + */ + public function GetDefinition() : b2BodyDef + { + var bd:b2BodyDef = new b2BodyDef(); + bd.type = GetType(); + bd.allowSleep = (m_flags & e_allowSleepFlag) == e_allowSleepFlag; + bd.angle = GetAngle(); + bd.angularDamping = m_angularDamping; + bd.angularVelocity = m_angularVelocity; + bd.fixedRotation = (m_flags & e_fixedRotationFlag) == e_fixedRotationFlag; + bd.bullet = (m_flags & e_bulletFlag) == e_bulletFlag; + bd.awake = (m_flags & e_awakeFlag) == e_awakeFlag; + bd.linearDamping = m_linearDamping; + bd.linearVelocity.SetV(GetLinearVelocity()); + bd.position = GetPosition(); + bd.userData = GetUserData(); + return bd; + } + + /** + * Apply a force at a world point. If the force is not + * applied at the center of mass, it will generate a torque and + * affect the angular velocity. This wakes up the body. + * @param force the world force vector, usually in Newtons (N). + * @param point the world position of the point of application. + */ + public function ApplyForce(force:b2Vec2, point:b2Vec2) : void{ + if (m_type != b2_dynamicBody) + { + return; + } + + if (IsAwake() == false) + { + SetAwake(true); + } + + //m_force += force; + m_force.x += force.x; + m_force.y += force.y; + //m_torque += b2Cross(point - m_sweep.c, force); + m_torque += ((point.x - m_sweep.c.x) * force.y - (point.y - m_sweep.c.y) * force.x); + } + + /** + * Apply a torque. This affects the angular velocity + * without affecting the linear velocity of the center of mass. + * This wakes up the body. + * @param torque about the z-axis (out of the screen), usually in N-m. + */ + public function ApplyTorque(torque:Number) : void { + if (m_type != b2_dynamicBody) + { + return; + } + + if (IsAwake() == false) + { + SetAwake(true); + } + m_torque += torque; + } + + /** + * Apply an impulse at a point. This immediately modifies the velocity. + * It also modifies the angular velocity if the point of application + * is not at the center of mass. This wakes up the body. + * @param impulse the world impulse vector, usually in N-seconds or kg-m/s. + * @param point the world position of the point of application. + */ + public function ApplyImpulse(impulse:b2Vec2, point:b2Vec2) : void{ + if (m_type != b2_dynamicBody) + { + return; + } + + if (IsAwake() == false) + { + SetAwake(true); + } + //m_linearVelocity += m_invMass * impulse; + m_linearVelocity.x += m_invMass * impulse.x; + m_linearVelocity.y += m_invMass * impulse.y; + //m_angularVelocity += m_invI * b2Cross(point - m_sweep.c, impulse); + m_angularVelocity += m_invI * ((point.x - m_sweep.c.x) * impulse.y - (point.y - m_sweep.c.y) * impulse.x); + } + + /** + * Splits a body into two, preserving dynamic properties + * @param callback Called once per fixture, return true to move this fixture to the new body + * function Callback(fixture:b2Fixture):Boolean + * @return The newly created bodies + * @asonly + */ + public function Split(callback:Function):b2Body + { + var linearVelocity:b2Vec2 = GetLinearVelocity().Copy();//Reset mass will alter this + var angularVelocity:Number = GetAngularVelocity(); + var center:b2Vec2 = GetWorldCenter(); + var body1:b2Body = this; + var body2:b2Body = m_world.CreateBody(GetDefinition()); + + var prev:b2Fixture; + for (var f:b2Fixture = body1.m_fixtureList; f; ) + { + if (callback(f)) + { + var next:b2Fixture = f.m_next; + // Remove fixture + if (prev) + { + prev.m_next = next; + }else { + body1.m_fixtureList = next; + } + body1.m_fixtureCount--; + + // Add fixture + f.m_next = body2.m_fixtureList; + body2.m_fixtureList = f; + body2.m_fixtureCount++; + + f.m_body = body2; + + f = next; + }else { + prev = f; + f = f.m_next + } + } + + body1.ResetMassData(); + body2.ResetMassData(); + + // Compute consistent velocites for new bodies based on cached velocity + var center1:b2Vec2 = body1.GetWorldCenter(); + var center2:b2Vec2 = body2.GetWorldCenter(); + + var velocity1:b2Vec2 = b2Math.AddVV(linearVelocity, + b2Math.CrossFV(angularVelocity, + b2Math.SubtractVV(center1, center))); + + var velocity2:b2Vec2 = b2Math.AddVV(linearVelocity, + b2Math.CrossFV(angularVelocity, + b2Math.SubtractVV(center2, center))); + + body1.SetLinearVelocity(velocity1); + body2.SetLinearVelocity(velocity2); + body1.SetAngularVelocity(angularVelocity); + body2.SetAngularVelocity(angularVelocity); + + body1.SynchronizeFixtures(); + body2.SynchronizeFixtures(); + + return body2; + } + + /** + * Merges another body into this. Only fixtures, mass and velocity are effected, + * Other properties are ignored + * @asonly + */ + public function Merge(other:b2Body):void + { + var f:b2Fixture; + for (f = other.m_fixtureList; f; ) + { + var next:b2Fixture = f.m_next; + + // Remove fixture + other.m_fixtureCount--; + + // Add fixture + f.m_next = m_fixtureList; + m_fixtureList = f; + m_fixtureCount++; + + f.m_body = body2; + + f = next; + } + body1.m_fixtureCount = 0; + + // Recalculate velocities + var body1:b2Body = this; + var body2:b2Body = other; + + // Compute consistent velocites for new bodies based on cached velocity + var center1:b2Vec2 = body1.GetWorldCenter(); + var center2:b2Vec2 = body2.GetWorldCenter(); + + var velocity1:b2Vec2 = body1.GetLinearVelocity().Copy(); + var velocity2:b2Vec2 = body2.GetLinearVelocity().Copy(); + + var angular1:Number = body1.GetAngularVelocity(); + var angular:Number = body2.GetAngularVelocity(); + + // TODO + + body1.ResetMassData(); + + SynchronizeFixtures(); + } + + /** + * Get the total mass of the body. + * @return the mass, usually in kilograms (kg). + */ + public function GetMass() : Number{ + return m_mass; + } + + /** + * Get the central rotational inertia of the body. + * @return the rotational inertia, usually in kg-m^2. + */ + public function GetInertia() : Number{ + return m_I; + } + + /** + * Get the mass data of the body. The rotational inertial is relative to the center of mass. + */ + public function GetMassData(data:b2MassData):void + { + data.mass = m_mass; + data.I = m_I; + data.center.SetV(m_sweep.localCenter); + } + + /** + * Set the mass properties to override the mass properties of the fixtures + * Note that this changes the center of mass position. + * Note that creating or destroying fixtures can also alter the mass. + * This function has no effect if the body isn't dynamic. + * @warning The supplied rotational inertia should be relative to the center of mass + * @param data the mass properties. + */ + public function SetMassData(massData:b2MassData):void + { + b2Settings.b2Assert(m_world.IsLocked() == false); + if (m_world.IsLocked() == true) + { + return; + } + + if (m_type != b2_dynamicBody) + { + return; + } + + m_invMass = 0.0; + m_I = 0.0; + m_invI = 0.0; + + m_mass = massData.mass; + + // Compute the center of mass. + if (m_mass <= 0.0) + { + m_mass = 1.0; + } + m_invMass = 1.0 / m_mass; + + if (massData.I > 0.0 && (m_flags & e_fixedRotationFlag) == 0) + { + // Center the inertia about the center of mass + m_I = massData.I - m_mass * (massData.center.x * massData.center.x + massData.center.y * massData.center.y); + m_invI = 1.0 / m_I; + } + + // Move center of mass + var oldCenter:b2Vec2 = m_sweep.c.Copy(); + m_sweep.localCenter.SetV(massData.center); + m_sweep.c0.SetV(b2Math.MulX(m_xf, m_sweep.localCenter)); + m_sweep.c.SetV(m_sweep.c0); + + // Update center of mass velocity + //m_linearVelocity += b2Cross(m_angularVelocity, m_sweep.c - oldCenter); + m_linearVelocity.x += m_angularVelocity * -(m_sweep.c.y - oldCenter.y); + m_linearVelocity.y += m_angularVelocity * +(m_sweep.c.x - oldCenter.x); + + } + + /** + * This resets the mass properties to the sum of the mass properties of the fixtures. + * This normally does not need to be called unless you called SetMassData to override + * the mass and later you want to reset the mass. + */ + public function ResetMassData():void + { + // Compute mass data from shapes. Each shape has it's own density + m_mass = 0.0; + m_invMass = 0.0; + m_I = 0.0; + m_invI = 0.0; + m_sweep.localCenter.SetZero(); + + // Static and kinematic bodies have zero mass. + if (m_type == b2_staticBody || m_type == b2_kinematicBody) + { + return; + } + //b2Assert(m_type == b2_dynamicBody); + + // Accumulate mass over all fixtures. + var center:b2Vec2 = b2Vec2.Make(0, 0); + for (var f:b2Fixture = m_fixtureList; f; f = f.m_next) + { + if (f.m_density == 0.0) + { + continue; + } + + var massData:b2MassData = f.GetMassData(); + m_mass += massData.mass; + center.x += massData.center.x * massData.mass; + center.y += massData.center.y * massData.mass; + m_I += massData.I; + } + + // Compute the center of mass. + if (m_mass > 0.0) + { + m_invMass = 1.0 / m_mass; + center.x *= m_invMass; + center.y *= m_invMass; + } + else + { + // Force all dynamic bodies to have a positive mass. + m_mass = 1.0; + m_invMass = 1.0; + } + + if (m_I > 0.0 && (m_flags & e_fixedRotationFlag) == 0) + { + // Center the inertia about the center of mass + m_I -= m_mass * (center.x * center.x + center.y * center.y); + m_I *= m_inertiaScale; + b2Settings.b2Assert(m_I > 0); + m_invI = 1.0 / m_I; + }else { + m_I = 0.0; + m_invI = 0.0; + } + + // Move center of mass + var oldCenter:b2Vec2 = m_sweep.c.Copy(); + m_sweep.localCenter.SetV(center); + m_sweep.c0.SetV(b2Math.MulX(m_xf, m_sweep.localCenter)); + m_sweep.c.SetV(m_sweep.c0); + + // Update center of mass velocity + //m_linearVelocity += b2Cross(m_angularVelocity, m_sweep.c - oldCenter); + m_linearVelocity.x += m_angularVelocity * -(m_sweep.c.y - oldCenter.y); + m_linearVelocity.y += m_angularVelocity * +(m_sweep.c.x - oldCenter.x); + + } + + /** + * Get the world coordinates of a point given the local coordinates. + * @param localPoint a point on the body measured relative the the body's origin. + * @return the same point expressed in world coordinates. + */ + public function GetWorldPoint(localPoint:b2Vec2) : b2Vec2{ + //return b2Math.b2MulX(m_xf, localPoint); + var A:b2Mat22 = m_xf.R; + var u:b2Vec2 = new b2Vec2(A.col1.x * localPoint.x + A.col2.x * localPoint.y, + A.col1.y * localPoint.x + A.col2.y * localPoint.y); + u.x += m_xf.position.x; + u.y += m_xf.position.y; + return u; + } + + /** + * Get the world coordinates of a vector given the local coordinates. + * @param localVector a vector fixed in the body. + * @return the same vector expressed in world coordinates. + */ + public function GetWorldVector(localVector:b2Vec2) : b2Vec2{ + return b2Math.MulMV(m_xf.R, localVector); + } + + /** + * Gets a local point relative to the body's origin given a world point. + * @param a point in world coordinates. + * @return the corresponding local point relative to the body's origin. + */ + public function GetLocalPoint(worldPoint:b2Vec2) : b2Vec2{ + return b2Math.MulXT(m_xf, worldPoint); + } + + /** + * Gets a local vector given a world vector. + * @param a vector in world coordinates. + * @return the corresponding local vector. + */ + public function GetLocalVector(worldVector:b2Vec2) : b2Vec2{ + return b2Math.MulTMV(m_xf.R, worldVector); + } + + /** + * Get the world linear velocity of a world point attached to this body. + * @param a point in world coordinates. + * @return the world velocity of a point. + */ + public function GetLinearVelocityFromWorldPoint(worldPoint:b2Vec2) : b2Vec2 + { + //return m_linearVelocity + b2Cross(m_angularVelocity, worldPoint - m_sweep.c); + return new b2Vec2(m_linearVelocity.x - m_angularVelocity * (worldPoint.y - m_sweep.c.y), + m_linearVelocity.y + m_angularVelocity * (worldPoint.x - m_sweep.c.x)); + } + + /** + * Get the world velocity of a local point. + * @param a point in local coordinates. + * @return the world velocity of a point. + */ + public function GetLinearVelocityFromLocalPoint(localPoint:b2Vec2) : b2Vec2 + { + //return GetLinearVelocityFromWorldPoint(GetWorldPoint(localPoint)); + var A:b2Mat22 = m_xf.R; + var worldPoint:b2Vec2 = new b2Vec2(A.col1.x * localPoint.x + A.col2.x * localPoint.y, + A.col1.y * localPoint.x + A.col2.y * localPoint.y); + worldPoint.x += m_xf.position.x; + worldPoint.y += m_xf.position.y; + return new b2Vec2(m_linearVelocity.x - m_angularVelocity * (worldPoint.y - m_sweep.c.y), + m_linearVelocity.y + m_angularVelocity * (worldPoint.x - m_sweep.c.x)); + } + + /** + * Get the linear damping of the body. + */ + public function GetLinearDamping():Number + { + return m_linearDamping; + } + + /** + * Set the linear damping of the body. + */ + public function SetLinearDamping(linearDamping:Number):void + { + m_linearDamping = linearDamping; + } + + /** + * Get the angular damping of the body + */ + public function GetAngularDamping():Number + { + return m_angularDamping; + } + + /** + * Set the angular damping of the body. + */ + public function SetAngularDamping(angularDamping:Number):void + { + m_angularDamping = angularDamping; + } + + /** + * Set the type of this body. This may alter the mass and velocity + * @param type - enum stored as a static member of b2Body + */ + public function SetType( type:uint ):void + { + if ( m_type == type ) + { + return; + } + + m_type = type; + + ResetMassData(); + + if ( m_type == b2_staticBody ) + { + m_linearVelocity.SetZero(); + m_angularVelocity = 0.0; + } + + SetAwake(true); + + m_force.SetZero(); + m_torque = 0.0; + + // Since the body type changed, we need to flag contacts for filtering. + for (var ce:b2ContactEdge = m_contactList; ce; ce = ce.next) + { + ce.contact.FlagForFiltering(); + } + } + + /** + * Get the type of this body. + * @return type enum as a uint + */ + public function GetType():uint + { + return m_type; + } + + /** + * Should this body be treated like a bullet for continuous collision detection? + */ + public function SetBullet(flag:Boolean) : void{ + if (flag) + { + m_flags |= e_bulletFlag; + } + else + { + m_flags &= ~e_bulletFlag; + } + } + + /** + * Is this body treated like a bullet for continuous collision detection? + */ + public function IsBullet() : Boolean{ + return (m_flags & e_bulletFlag) == e_bulletFlag; + } + + /** + * Is this body allowed to sleep + * @param flag + */ + public function SetSleepingAllowed(flag:Boolean):void{ + if (flag) + { + m_flags |= e_allowSleepFlag; + } + else + { + m_flags &= ~e_allowSleepFlag; + SetAwake(true); + } + } + + /** + * Set the sleep state of the body. A sleeping body has vety low CPU cost. + * @param flag - set to true to put body to sleep, false to wake it + */ + public function SetAwake(flag:Boolean):void { + if (flag) + { + m_flags |= e_awakeFlag; + m_sleepTime = 0.0; + } + else + { + m_flags &= ~e_awakeFlag; + m_sleepTime = 0.0; + m_linearVelocity.SetZero(); + m_angularVelocity = 0.0; + m_force.SetZero(); + m_torque = 0.0; + } + } + + /** + * Get the sleeping state of this body. + * @return true if body is sleeping + */ + public function IsAwake():Boolean { + return (m_flags & e_awakeFlag) == e_awakeFlag; + } + + /** + * Set this body to have fixed rotation. This causes the mass to be reset. + * @param fixed - true means no rotation + */ + public function SetFixedRotation(fixed:Boolean):void + { + if(fixed) + { + m_flags |= e_fixedRotationFlag; + } + else + { + m_flags &= ~e_fixedRotationFlag; + } + + ResetMassData(); + } + + /** + * Does this body have fixed rotation? + * @return true means fixed rotation + */ + public function IsFixedRotation():Boolean + { + return (m_flags & e_fixedRotationFlag)==e_fixedRotationFlag; + } + + /** Set the active state of the body. An inactive body is not + * simulated and cannot be collided with or woken up. + * If you pass a flag of true, all fixtures will be added to the + * broad-phase. + * If you pass a flag of false, all fixtures will be removed from + * the broad-phase and all contacts will be destroyed. + * Fixtures and joints are otherwise unaffected. You may continue + * to create/destroy fixtures and joints on inactive bodies. + * Fixtures on an inactive body are implicitly inactive and will + * not participate in collisions, ray-casts, or queries. + * Joints connected to an inactive body are implicitly inactive. + * An inactive body is still owned by a b2World object and remains + * in the body list. + */ + public function SetActive( flag:Boolean ):void{ + if (flag == IsActive()) + { + return; + } + + var broadPhase:IBroadPhase; + var f:b2Fixture; + if (flag) + { + m_flags |= e_activeFlag; + + // Create all proxies. + broadPhase = m_world.m_contactManager.m_broadPhase; + for ( f = m_fixtureList; f; f = f.m_next) + { + f.CreateProxy(broadPhase, m_xf); + } + // Contacts are created the next time step. + } + else + { + m_flags &= ~e_activeFlag; + + // Destroy all proxies. + broadPhase = m_world.m_contactManager.m_broadPhase; + for ( f = m_fixtureList; f; f = f.m_next) + { + f.DestroyProxy(broadPhase); + } + + // Destroy the attached contacts. + var ce:b2ContactEdge = m_contactList; + while (ce) + { + var ce0:b2ContactEdge = ce; + ce = ce.next; + m_world.m_contactManager.Destroy(ce0.contact); + } + m_contactList = null; + } + } + + /** + * Get the active state of the body. + * @return true if active. + */ + public function IsActive():Boolean{ + return (m_flags & e_activeFlag) == e_activeFlag; + } + + /** + * Is this body allowed to sleep? + */ + public function IsSleepingAllowed():Boolean + { + return(m_flags & e_allowSleepFlag) == e_allowSleepFlag; + } + + /** + * Get the list of all fixtures attached to this body. + */ + public function GetFixtureList() : b2Fixture{ + return m_fixtureList; + } + + /** + * Get the list of all joints attached to this body. + */ + public function GetJointList() : b2JointEdge{ + return m_jointList; + } + + /** + * Get the list of all controllers attached to this body. + */ + public function GetControllerList() : b2ControllerEdge { + return m_controllerList; + } + + /** + * Get a list of all contacts attached to this body. + */ + public function GetContactList():b2ContactEdge { + return m_contactList; + } + + /** + * Get the next body in the world's body list. + */ + public function GetNext() : b2Body{ + return m_next; + } + + /** + * Get the user data pointer that was provided in the body definition. + */ + public function GetUserData() : *{ + return m_userData; + } + + /** + * Set the user data. Use this to store your application specific data. + */ + public function SetUserData(data:*) : void + { + m_userData = data; + } + + /** + * Get the parent world of this body. + */ + public function GetWorld(): b2World + { + return m_world; + } + + //--------------- Internals Below ------------------- + + + // Constructor + /** + * @private + */ + public function b2Body(bd:b2BodyDef, world:b2World){ + //b2Settings.b2Assert(world.IsLocked() == false); + + //b2Settings.b2Assert(bd.position.IsValid()); + //b2Settings.b2Assert(bd.linearVelocity.IsValid()); + //b2Settings.b2Assert(b2Math.b2IsValid(bd.angle)); + //b2Settings.b2Assert(b2Math.b2IsValid(bd.angularVelocity)); + //b2Settings.b2Assert(b2Math.b2IsValid(bd.inertiaScale) && bd.inertiaScale >= 0.0); + //b2Settings.b2Assert(b2Math.b2IsValid(bd.angularDamping) && bd.angularDamping >= 0.0); + //b2Settings.b2Assert(b2Math.b2IsValid(bd.linearDamping) && bd.linearDamping >= 0.0); + + m_flags = 0; + + if (bd.bullet ) + { + m_flags |= e_bulletFlag; + } + if (bd.fixedRotation) + { + m_flags |= e_fixedRotationFlag; + } + if (bd.allowSleep) + { + m_flags |= e_allowSleepFlag; + } + if (bd.awake) + { + m_flags |= e_awakeFlag; + } + if (bd.active) + { + m_flags |= e_activeFlag; + } + + m_world = world; + + m_xf.position.SetV(bd.position); + m_xf.R.Set(bd.angle); + + m_sweep.localCenter.SetZero(); + m_sweep.t0 = 1.0; + m_sweep.a0 = m_sweep.a = bd.angle; + + //m_sweep.c0 = m_sweep.c = b2Mul(m_xf, m_sweep.localCenter); + //b2MulMV(m_xf.R, m_sweep.localCenter); + var tMat:b2Mat22 = m_xf.R; + var tVec:b2Vec2 = m_sweep.localCenter; + // (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y) + m_sweep.c.x = (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + // (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y) + m_sweep.c.y = (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + //return T.position + b2Mul(T.R, v); + m_sweep.c.x += m_xf.position.x; + m_sweep.c.y += m_xf.position.y; + //m_sweep.c0 = m_sweep.c + m_sweep.c0.SetV(m_sweep.c); + + m_jointList = null; + m_controllerList = null; + m_contactList = null; + m_controllerCount = 0; + m_prev = null; + m_next = null; + + m_linearVelocity.SetV(bd.linearVelocity); + m_angularVelocity = bd.angularVelocity; + + m_linearDamping = bd.linearDamping; + m_angularDamping = bd.angularDamping; + + m_force.Set(0.0, 0.0); + m_torque = 0.0; + + m_sleepTime = 0.0; + + m_type = bd.type; + + if (m_type == b2_dynamicBody) + { + m_mass = 1.0; + m_invMass = 1.0; + } + else + { + m_mass = 0.0; + m_invMass = 0.0; + } + + m_I = 0.0; + m_invI = 0.0; + + m_inertiaScale = bd.inertiaScale; + + m_userData = bd.userData; + + m_fixtureList = null; + m_fixtureCount = 0; + } + + // Destructor + //~b2Body(); + + // + static private var s_xf1:b2Transform = new b2Transform(); + // + b2internal function SynchronizeFixtures() : void{ + + var xf1:b2Transform = s_xf1; + xf1.R.Set(m_sweep.a0); + //xf1.position = m_sweep.c0 - b2Mul(xf1.R, m_sweep.localCenter); + var tMat:b2Mat22 = xf1.R; + var tVec:b2Vec2 = m_sweep.localCenter; + xf1.position.x = m_sweep.c0.x - (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + xf1.position.y = m_sweep.c0.y - (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + + var f:b2Fixture; + var broadPhase:IBroadPhase = m_world.m_contactManager.m_broadPhase; + for (f = m_fixtureList; f; f = f.m_next) + { + f.Synchronize(broadPhase, xf1, m_xf); + } + } + + b2internal function SynchronizeTransform() : void{ + m_xf.R.Set(m_sweep.a); + //m_xf.position = m_sweep.c - b2Mul(m_xf.R, m_sweep.localCenter); + var tMat:b2Mat22 = m_xf.R; + var tVec:b2Vec2 = m_sweep.localCenter; + m_xf.position.x = m_sweep.c.x - (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y); + m_xf.position.y = m_sweep.c.y - (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y); + } + + // This is used to prevent connected bodies from colliding. + // It may lie, depending on the collideConnected flag. + b2internal function ShouldCollide(other:b2Body) : Boolean { + // At least one body should be dynamic + if (m_type != b2_dynamicBody && other.m_type != b2_dynamicBody ) + { + return false; + } + // Does a joint prevent collision? + for (var jn:b2JointEdge = m_jointList; jn; jn = jn.next) + { + if (jn.other == other) + if (jn.joint.m_collideConnected == false) + { + return false; + } + } + + return true; + } + + b2internal function Advance(t:Number) : void{ + // Advance to the new safe time. + m_sweep.Advance(t); + m_sweep.c.SetV(m_sweep.c0); + m_sweep.a = m_sweep.a0; + SynchronizeTransform(); + } + + b2internal var m_flags:uint; + b2internal var m_type:int; + + b2internal var m_islandIndex:int; + + b2internal var m_xf:b2Transform = new b2Transform(); // the body origin transform + + b2internal var m_sweep:b2Sweep = new b2Sweep(); // the swept motion for CCD + + b2internal var m_linearVelocity:b2Vec2 = new b2Vec2(); + b2internal var m_angularVelocity:Number; + + b2internal var m_force:b2Vec2 = new b2Vec2(); + b2internal var m_torque:Number; + + b2internal var m_world:b2World; + b2internal var m_prev:b2Body; + b2internal var m_next:b2Body; + + b2internal var m_fixtureList:b2Fixture; + b2internal var m_fixtureCount:int; + + b2internal var m_controllerList:b2ControllerEdge; + b2internal var m_controllerCount:int; + + b2internal var m_jointList:b2JointEdge; + b2internal var m_contactList:b2ContactEdge; + + b2internal var m_mass:Number, m_invMass:Number; + b2internal var m_I:Number, m_invI:Number; + + b2internal var m_inertiaScale:Number; + + b2internal var m_linearDamping:Number; + b2internal var m_angularDamping:Number; + + b2internal var m_sleepTime:Number; + + private var m_userData:*; + + + // m_flags + //enum + //{ + static b2internal var e_islandFlag:uint = 0x0001; + static b2internal var e_awakeFlag:uint = 0x0002; + static b2internal var e_allowSleepFlag:uint = 0x0004; + static b2internal var e_bulletFlag:uint = 0x0008; + static b2internal var e_fixedRotationFlag:uint = 0x0010; + static b2internal var e_activeFlag:uint = 0x0020; + //}; + + // m_type + //enum + //{ + /// The body type. + /// static: zero mass, zero velocity, may be manually moved + /// kinematic: zero mass, non-zero velocity set by user, moved by solver + /// dynamic: positive mass, non-zero velocity determined by forces, moved by solver + static public var b2_staticBody:uint = 0; + static public var b2_kinematicBody:uint = 1; + static public var b2_dynamicBody:uint = 2; + //}; + +}; + +} diff --git a/srclib/Box2D/Dynamics/b2BodyDef.as b/srclib/Box2D/Dynamics/b2BodyDef.as new file mode 100644 index 00000000..f1c8460a --- /dev/null +++ b/srclib/Box2D/Dynamics/b2BodyDef.as @@ -0,0 +1,139 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + + import Box2D.Common.Math.*; + import Box2D.Common.b2internal; +use namespace b2internal; + + +/** +* A body definition holds all the data needed to construct a rigid body. +* You can safely re-use body definitions. +*/ +public class b2BodyDef +{ + /** + * This constructor sets the body definition default values. + */ + public function b2BodyDef() + { + userData = null; + position.Set(0.0, 0.0); + angle = 0.0; + linearVelocity.Set(0, 0); + angularVelocity = 0.0; + linearDamping = 0.0; + angularDamping = 0.0; + allowSleep = true; + awake = true; + fixedRotation = false; + bullet = false; + type = b2Body.b2_staticBody; + active = true; + inertiaScale = 1.0; + } + + /** The body type: static, kinematic, or dynamic. A member of the b2BodyType class + * Note: if a dynamic body would have zero mass, the mass is set to one. + * @see b2Body#b2_staticBody + * @see b2Body#b2_dynamicBody + * @see b2Body#b2_kinematicBody + */ + public var type:uint; + + /** + * The world position of the body. Avoid creating bodies at the origin + * since this can lead to many overlapping shapes. + */ + public var position:b2Vec2 = new b2Vec2(); + + /** + * The world angle of the body in radians. + */ + public var angle:Number; + + /** + * The linear velocity of the body's origin in world co-ordinates. + */ + public var linearVelocity:b2Vec2 = new b2Vec2(); + + /** + * The angular velocity of the body. + */ + public var angularVelocity:Number; + + /** + * Linear damping is use to reduce the linear velocity. The damping parameter + * can be larger than 1.0f but the damping effect becomes sensitive to the + * time step when the damping parameter is large. + */ + public var linearDamping:Number; + + /** + * Angular damping is use to reduce the angular velocity. The damping parameter + * can be larger than 1.0f but the damping effect becomes sensitive to the + * time step when the damping parameter is large. + */ + public var angularDamping:Number; + + /** + * Set this flag to false if this body should never fall asleep. Note that + * this increases CPU usage. + */ + public var allowSleep:Boolean; + + /** + * Is this body initially awake or sleeping? + */ + public var awake:Boolean; + + /** + * Should this body be prevented from rotating? Useful for characters. + */ + public var fixedRotation:Boolean; + + /** + * Is this a fast moving body that should be prevented from tunneling through + * other moving bodies? Note that all bodies are prevented from tunneling through + * static bodies. + * @warning You should use this flag sparingly since it increases processing time. + */ + public var bullet:Boolean; + + /** + * Does this body start out active? + */ + public var active:Boolean; + + /** + * Use this to store application specific body data. + */ + public var userData:*; + + /** + * Scales the inertia tensor. + * @warning Experimental + */ + public var inertiaScale:Number; +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/b2ContactFilter.as b/srclib/Box2D/Dynamics/b2ContactFilter.as new file mode 100644 index 00000000..08408081 --- /dev/null +++ b/srclib/Box2D/Dynamics/b2ContactFilter.as @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + + import Box2D.Common.*; +use namespace b2internal; + + +/** +* Implement this class to provide collision filtering. In other words, you can implement +* this class if you want finer control over contact creation. +*/ +public class b2ContactFilter +{ + + /** + * Return true if contact calculations should be performed between these two fixtures. + * @warning for performance reasons this is only called when the AABBs begin to overlap. + */ + public virtual function ShouldCollide(fixtureA:b2Fixture, fixtureB:b2Fixture) : Boolean{ + var filter1:b2FilterData = fixtureA.GetFilterData(); + var filter2:b2FilterData = fixtureB.GetFilterData(); + + if (filter1.groupIndex == filter2.groupIndex && filter1.groupIndex != 0) + { + return filter1.groupIndex > 0; + } + + var collide:Boolean = (filter1.maskBits & filter2.categoryBits) != 0 && (filter1.categoryBits & filter2.maskBits) != 0; + return collide; + } + + /** + * Return true if the given fixture should be considered for ray intersection. + * By default, userData is cast as a b2Fixture and collision is resolved according to ShouldCollide + * @see ShouldCollide() + * @see b2World#Raycast + * @param userData arbitrary data passed from Raycast or RaycastOne + * @param fixture the fixture that we are testing for filtering + * @return a Boolean, with a value of false indicating that this fixture should be ignored. + */ + public virtual function RayCollide(userData:*, fixture:b2Fixture) : Boolean{ + if(!userData) + return true; + return ShouldCollide(userData as b2Fixture,fixture); + } + + static b2internal var b2_defaultFilter:b2ContactFilter = new b2ContactFilter(); + +}; + +} diff --git a/srclib/Box2D/Dynamics/b2ContactImpulse.as b/srclib/Box2D/Dynamics/b2ContactImpulse.as new file mode 100644 index 00000000..fc001306 --- /dev/null +++ b/srclib/Box2D/Dynamics/b2ContactImpulse.as @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics +{ + + import Box2D.Common.b2Settings; + + /** + * Contact impulses for reporting. Impulses are used instead of forces because + * sub-step forces may approach infinity for rigid body collisions. These + * match up one-to-one with the contact points in b2Manifold. + */ + public class b2ContactImpulse + { + + public function b2ContactImpulse() {} + public var normalImpulses:Vector. = new Vector.(b2Settings.b2_maxManifoldPoints); + public var tangentImpulses:Vector. = new Vector.(b2Settings.b2_maxManifoldPoints); + + } + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/b2ContactListener.as b/srclib/Box2D/Dynamics/b2ContactListener.as new file mode 100644 index 00000000..afb309d2 --- /dev/null +++ b/srclib/Box2D/Dynamics/b2ContactListener.as @@ -0,0 +1,78 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + + import Box2D.Collision.*; + import Box2D.Common.*; + import Box2D.Dynamics.Contacts.*; +use namespace b2internal; + + +/** + * Implement this class to get contact information. You can use these results for + * things like sounds and game logic. You can also get contact results by + * traversing the contact lists after the time step. However, you might miss + * some contacts because continuous physics leads to sub-stepping. + * Additionally you may receive multiple callbacks for the same contact in a + * single time step. + * You should strive to make your callbacks efficient because there may be + * many callbacks per time step. + * @warning You cannot create/destroy Box2D entities inside these callbacks. + */ +public class b2ContactListener +{ + /** + * Called when two fixtures begin to touch. + */ + public virtual function BeginContact(contact:b2Contact):void { } + + /** + * Called when two fixtures cease to touch. + */ + public virtual function EndContact(contact:b2Contact):void { } + + /** + * This is called after a contact is updated. This allows you to inspect a + * contact before it goes to the solver. If you are careful, you can modify the + * contact manifold (e.g. disable contact). + * A copy of the old manifold is provided so that you can detect changes. + * Note: this is called only for awake bodies. + * Note: this is called even when the number of contact points is zero. + * Note: this is not called for sensors. + * Note: if you set the number of contact points to zero, you will not + * get an EndContact callback. However, you may get a BeginContact callback + * the next step. + */ + public virtual function PreSolve(contact:b2Contact, oldManifold:b2Manifold):void {} + + /** + * This lets you inspect a contact after the solver is finished. This is useful + * for inspecting impulses. + * Note: the contact manifold does not include time of impact impulses, which can be + * arbitrarily large if the sub-step is small. Hence the impulse is provided explicitly + * in a separate data structure. + * Note: this is only called for contacts that are touching, solid, and awake. + */ + public virtual function PostSolve(contact:b2Contact, impulse:b2ContactImpulse):void { } + + b2internal static var b2_defaultListener:b2ContactListener = new b2ContactListener(); +}; + +} diff --git a/srclib/Box2D/Dynamics/b2ContactManager.as b/srclib/Box2D/Dynamics/b2ContactManager.as new file mode 100644 index 00000000..ba255c99 --- /dev/null +++ b/srclib/Box2D/Dynamics/b2ContactManager.as @@ -0,0 +1,282 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + + import Box2D.Collision.*; + import Box2D.Common.*; + import Box2D.Dynamics.Contacts.*; +use namespace b2internal; + + +// Delegate of b2World. +/** +* @private +*/ +public class b2ContactManager +{ + public function b2ContactManager() { + m_world = null; + m_contactCount = 0; + m_contactFilter = b2ContactFilter.b2_defaultFilter; + m_contactListener = b2ContactListener.b2_defaultListener; + m_contactFactory = new b2ContactFactory(m_allocator); + m_broadPhase = new b2DynamicTreeBroadPhase(); + }; + + // This is a callback from the broadphase when two AABB proxies begin + // to overlap. We create a b2Contact to manage the narrow phase. + public function AddPair(proxyUserDataA:*, proxyUserDataB:*):void { + var fixtureA:b2Fixture = proxyUserDataA as b2Fixture; + var fixtureB:b2Fixture = proxyUserDataB as b2Fixture; + + var bodyA:b2Body = fixtureA.GetBody(); + var bodyB:b2Body = fixtureB.GetBody(); + + // Are the fixtures on the same body? + if (bodyA == bodyB) + return; + + // Does a contact already exist? + var edge:b2ContactEdge = bodyB.GetContactList(); + while (edge) + { + if (edge.other == bodyA) + { + var fA:b2Fixture = edge.contact.GetFixtureA(); + var fB:b2Fixture = edge.contact.GetFixtureB(); + if (fA == fixtureA && fB == fixtureB) + return; + if (fA == fixtureB && fB == fixtureA) + return; + } + edge = edge.next; + } + + //Does a joint override collision? Is at least one body dynamic? + if (bodyB.ShouldCollide(bodyA) == false) + { + return; + } + + // Check user filtering + if (m_contactFilter.ShouldCollide(fixtureA, fixtureB) == false) + { + return; + } + + // Call the factory. + var c:b2Contact = m_contactFactory.Create(fixtureA, fixtureB); + + // Contact creation may swap shapes. + fixtureA = c.GetFixtureA(); + fixtureB = c.GetFixtureB(); + bodyA = fixtureA.m_body; + bodyB = fixtureB.m_body; + + // Insert into the world. + c.m_prev = null; + c.m_next = m_world.m_contactList; + if (m_world.m_contactList != null) + { + m_world.m_contactList.m_prev = c; + } + m_world.m_contactList = c; + + + // Connect to island graph. + + // Connect to body A + c.m_nodeA.contact = c; + c.m_nodeA.other = bodyB; + + c.m_nodeA.prev = null; + c.m_nodeA.next = bodyA.m_contactList; + if (bodyA.m_contactList != null) + { + bodyA.m_contactList.prev = c.m_nodeA; + } + bodyA.m_contactList = c.m_nodeA; + + // Connect to body 2 + c.m_nodeB.contact = c; + c.m_nodeB.other = bodyA; + + c.m_nodeB.prev = null; + c.m_nodeB.next = bodyB.m_contactList; + if (bodyB.m_contactList != null) + { + bodyB.m_contactList.prev = c.m_nodeB; + } + bodyB.m_contactList = c.m_nodeB; + + ++m_world.m_contactCount; + return; + + } + + public function FindNewContacts():void + { + m_broadPhase.UpdatePairs(AddPair); + } + + static private const s_evalCP:b2ContactPoint = new b2ContactPoint(); + public function Destroy(c:b2Contact) : void + { + + var fixtureA:b2Fixture = c.GetFixtureA(); + var fixtureB:b2Fixture = c.GetFixtureB(); + var bodyA:b2Body = fixtureA.GetBody(); + var bodyB:b2Body = fixtureB.GetBody(); + + if (c.IsTouching()) + { + m_contactListener.EndContact(c); + } + + // Remove from the world. + if (c.m_prev) + { + c.m_prev.m_next = c.m_next; + } + + if (c.m_next) + { + c.m_next.m_prev = c.m_prev; + } + + if (c == m_world.m_contactList) + { + m_world.m_contactList = c.m_next; + } + + // Remove from body A + if (c.m_nodeA.prev) + { + c.m_nodeA.prev.next = c.m_nodeA.next; + } + + if (c.m_nodeA.next) + { + c.m_nodeA.next.prev = c.m_nodeA.prev; + } + + if (c.m_nodeA == bodyA.m_contactList) + { + bodyA.m_contactList = c.m_nodeA.next; + } + + // Remove from body 2 + if (c.m_nodeB.prev) + { + c.m_nodeB.prev.next = c.m_nodeB.next; + } + + if (c.m_nodeB.next) + { + c.m_nodeB.next.prev = c.m_nodeB.prev; + } + + if (c.m_nodeB == bodyB.m_contactList) + { + bodyB.m_contactList = c.m_nodeB.next; + } + + // Call the factory. + m_contactFactory.Destroy(c); + --m_contactCount; + } + + + // This is the top level collision call for the time step. Here + // all the narrow phase collision is processed for the world + // contact list. + public function Collide() : void + { + // Update awake contacts. + var c:b2Contact = m_world.m_contactList; + while (c) + { + var fixtureA:b2Fixture = c.GetFixtureA(); + var fixtureB:b2Fixture = c.GetFixtureB(); + var bodyA:b2Body = fixtureA.GetBody(); + var bodyB:b2Body = fixtureB.GetBody(); + if (bodyA.IsAwake() == false && bodyB.IsAwake() == false) + { + c = c.GetNext(); + continue; + } + + // Is this contact flagged for filtering? + if (c.m_flags & b2Contact.e_filterFlag) + { + // Should these bodies collide? + if (bodyB.ShouldCollide(bodyA) == false) + { + var cNuke:b2Contact = c; + c = cNuke.GetNext(); + Destroy(cNuke); + continue; + } + + // Check user filtering. + if (m_contactFilter.ShouldCollide(fixtureA, fixtureB) == false) + { + cNuke = c; + c = cNuke.GetNext(); + Destroy(cNuke); + continue; + } + + // Clear the filtering flag + c.m_flags &= ~b2Contact.e_filterFlag; + } + + var proxyA:* = fixtureA.m_proxy; + var proxyB:* = fixtureB.m_proxy; + + var overlap:Boolean = m_broadPhase.TestOverlap(proxyA, proxyB); + + // Here we destroy contacts that cease to overlap in the broadphase + if ( overlap == false) + { + cNuke = c; + c = cNuke.GetNext(); + Destroy(cNuke); + continue; + } + + c.Update(m_contactListener); + c = c.GetNext(); + } + } + + + b2internal var m_world:b2World; + b2internal var m_broadPhase:IBroadPhase; + + b2internal var m_contactList:b2Contact; + b2internal var m_contactCount:int; + b2internal var m_contactFilter:b2ContactFilter; + b2internal var m_contactListener:b2ContactListener; + b2internal var m_contactFactory:b2ContactFactory; + b2internal var m_allocator:*; +}; + +} diff --git a/srclib/Box2D/Dynamics/b2DebugDraw.as b/srclib/Box2D/Dynamics/b2DebugDraw.as new file mode 100644 index 00000000..f9ef5d6c --- /dev/null +++ b/srclib/Box2D/Dynamics/b2DebugDraw.as @@ -0,0 +1,267 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + + import Box2D.Common.*; + import Box2D.Common.Math.*; + + import flash.display.Sprite; +use namespace b2internal; + + + +/** +* Implement and register this class with a b2World to provide debug drawing of physics +* entities in your game. +*/ +public class b2DebugDraw +{ + + public function b2DebugDraw(){ + m_drawFlags = 0; + } + + //virtual ~b2DebugDraw() {} + + //enum + //{ + /** Draw shapes */ + static public var e_shapeBit:uint = 0x0001; + /** Draw joint connections */ + static public var e_jointBit:uint = 0x0002; + /** Draw axis aligned bounding boxes */ + static public var e_aabbBit:uint = 0x0004; + /** Draw broad-phase pairs */ + static public var e_pairBit:uint = 0x0008; + /** Draw center of mass frame */ + static public var e_centerOfMassBit:uint = 0x0010; + /** Draw controllers */ + static public var e_controllerBit:uint = 0x0020; + //}; + + /** + * Set the drawing flags. + */ + public function SetFlags(flags:uint) : void{ + m_drawFlags = flags; + } + + /** + * Get the drawing flags. + */ + public function GetFlags() : uint{ + return m_drawFlags; + } + + /** + * Append flags to the current flags. + */ + public function AppendFlags(flags:uint) : void{ + m_drawFlags |= flags; + } + + /** + * Clear flags from the current flags. + */ + public function ClearFlags(flags:uint) : void { + m_drawFlags &= ~flags; + } + + /** + * Set the sprite + */ + public function SetSprite(sprite:Sprite) : void { + m_sprite = sprite; + } + + /** + * Get the sprite + */ + public function GetSprite() : Sprite { + return m_sprite; + } + + /** + * Set the draw scale + */ + public function SetDrawScale(drawScale:Number) : void { + m_drawScale = drawScale; + } + + /** + * Get the draw + */ + public function GetDrawScale() : Number { + return m_drawScale; + } + + /** + * Set the line thickness + */ + public function SetLineThickness(lineThickness:Number) : void { + m_lineThickness = lineThickness; + } + + /** + * Get the line thickness + */ + public function GetLineThickness() : Number { + return m_lineThickness; + } + + /** + * Set the alpha value used for lines + */ + public function SetAlpha(alpha:Number) : void { + m_alpha = alpha; + } + + /** + * Get the alpha value used for lines + */ + public function GetAlpha() : Number { + return m_alpha; + } + + /** + * Set the alpha value used for fills + */ + public function SetFillAlpha(alpha:Number) : void { + m_fillAlpha = alpha; + } + + /** + * Get the alpha value used for fills + */ + public function GetFillAlpha() : Number { + return m_fillAlpha; + } + + /** + * Set the scale used for drawing XForms + */ + public function SetXFormScale(xformScale:Number) : void { + m_xformScale = xformScale; + } + + /** + * Get the scale used for drawing XForms + */ + public function GetXFormScale() : Number { + return m_xformScale; + } + + /** + * Draw a closed polygon provided in CCW order. + */ + public virtual function DrawPolygon(vertices:Array, vertexCount:int, color:b2Color) : void{ + + m_sprite.graphics.lineStyle(m_lineThickness, color.color, m_alpha); + m_sprite.graphics.moveTo(vertices[0].x * m_drawScale, vertices[0].y * m_drawScale); + for (var i:int = 1; i < vertexCount; i++){ + m_sprite.graphics.lineTo(vertices[i].x * m_drawScale, vertices[i].y * m_drawScale); + } + m_sprite.graphics.lineTo(vertices[0].x * m_drawScale, vertices[0].y * m_drawScale); + + } + + /** + * Draw a solid closed polygon provided in CCW order. + */ + public virtual function DrawSolidPolygon(vertices:Vector., vertexCount:int, color:b2Color) : void{ + + m_sprite.graphics.lineStyle(m_lineThickness, color.color, m_alpha); + m_sprite.graphics.moveTo(vertices[0].x * m_drawScale, vertices[0].y * m_drawScale); + m_sprite.graphics.beginFill(color.color, m_fillAlpha); + for (var i:int = 1; i < vertexCount; i++){ + m_sprite.graphics.lineTo(vertices[i].x * m_drawScale, vertices[i].y * m_drawScale); + } + m_sprite.graphics.lineTo(vertices[0].x * m_drawScale, vertices[0].y * m_drawScale); + m_sprite.graphics.endFill(); + + } + + /** + * Draw a circle. + */ + public virtual function DrawCircle(center:b2Vec2, radius:Number, color:b2Color) : void{ + + m_sprite.graphics.lineStyle(m_lineThickness, color.color, m_alpha); + m_sprite.graphics.drawCircle(center.x * m_drawScale, center.y * m_drawScale, radius * m_drawScale); + + } + + /** + * Draw a solid circle. + */ + public virtual function DrawSolidCircle(center:b2Vec2, radius:Number, axis:b2Vec2, color:b2Color) : void{ + + m_sprite.graphics.lineStyle(m_lineThickness, color.color, m_alpha); + m_sprite.graphics.moveTo(0,0); + m_sprite.graphics.beginFill(color.color, m_fillAlpha); + m_sprite.graphics.drawCircle(center.x * m_drawScale, center.y * m_drawScale, radius * m_drawScale); + m_sprite.graphics.endFill(); + m_sprite.graphics.moveTo(center.x * m_drawScale, center.y * m_drawScale); + m_sprite.graphics.lineTo((center.x + axis.x*radius) * m_drawScale, (center.y + axis.y*radius) * m_drawScale); + + } + + + /** + * Draw a line segment. + */ + public virtual function DrawSegment(p1:b2Vec2, p2:b2Vec2, color:b2Color) : void{ + + m_sprite.graphics.lineStyle(m_lineThickness, color.color, m_alpha); + m_sprite.graphics.moveTo(p1.x * m_drawScale, p1.y * m_drawScale); + m_sprite.graphics.lineTo(p2.x * m_drawScale, p2.y * m_drawScale); + + } + + /** + * Draw a transform. Choose your own length scale. + * @param xf a transform. + */ + public virtual function DrawTransform(xf:b2Transform) : void{ + + m_sprite.graphics.lineStyle(m_lineThickness, 0xff0000, m_alpha); + m_sprite.graphics.moveTo(xf.position.x * m_drawScale, xf.position.y * m_drawScale); + m_sprite.graphics.lineTo((xf.position.x + m_xformScale*xf.R.col1.x) * m_drawScale, (xf.position.y + m_xformScale*xf.R.col1.y) * m_drawScale); + + m_sprite.graphics.lineStyle(m_lineThickness, 0x00ff00, m_alpha); + m_sprite.graphics.moveTo(xf.position.x * m_drawScale, xf.position.y * m_drawScale); + m_sprite.graphics.lineTo((xf.position.x + m_xformScale*xf.R.col2.x) * m_drawScale, (xf.position.y + m_xformScale*xf.R.col2.y) * m_drawScale); + + } + + + + private var m_drawFlags:uint; + b2internal var m_sprite:Sprite; + private var m_drawScale:Number = 1.0; + + private var m_lineThickness:Number = 1.0; + private var m_alpha:Number = 1.0; + private var m_fillAlpha:Number = 1.0; + private var m_xformScale:Number = 1.0; + +}; + +} diff --git a/srclib/Box2D/Dynamics/b2DestructionListener.as b/srclib/Box2D/Dynamics/b2DestructionListener.as new file mode 100644 index 00000000..0a80085d --- /dev/null +++ b/srclib/Box2D/Dynamics/b2DestructionListener.as @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + + import Box2D.Common.*; + import Box2D.Dynamics.Joints.*; +use namespace b2internal; + + +/** +* Joints and shapes are destroyed when their associated +* body is destroyed. Implement this listener so that you +* may nullify references to these joints and shapes. +*/ +public class b2DestructionListener +{ + + /** + * Called when any joint is about to be destroyed due + * to the destruction of one of its attached bodies. + */ + public virtual function SayGoodbyeJoint(joint:b2Joint) : void{}; + + /** + * Called when any fixture is about to be destroyed due + * to the destruction of its parent body. + */ + public virtual function SayGoodbyeFixture(fixture:b2Fixture) : void{}; + +}; + +} diff --git a/srclib/Box2D/Dynamics/b2FilterData.as b/srclib/Box2D/Dynamics/b2FilterData.as new file mode 100644 index 00000000..99c8a7f8 --- /dev/null +++ b/srclib/Box2D/Dynamics/b2FilterData.as @@ -0,0 +1,61 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + + + import Box2D.Common.b2internal; +use namespace b2internal; + + +/** +* This holds contact filtering data. +*/ +public class b2FilterData +{ + public function b2FilterData() {} + + public function Copy() : b2FilterData { + var copy: b2FilterData = new b2FilterData(); + copy.categoryBits = categoryBits; + copy.maskBits = maskBits; + copy.groupIndex = groupIndex; + return copy; + } + + /** + * The collision category bits. Normally you would just set one bit. + */ + public var categoryBits: uint = 0x0001; + + /** + * The collision mask bits. This states the categories that this + * shape would accept for collision. + */ + public var maskBits: uint = 0xFFFF; + + /** + * Collision groups allow a certain group of objects to never collide (negative) + * or always collide (positive). Zero means no collision group. Non-zero group + * filtering always wins against the mask bits. + */ + public var groupIndex: int = 0; +} + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/b2Fixture.as b/srclib/Box2D/Dynamics/b2Fixture.as new file mode 100644 index 00000000..97b5e594 --- /dev/null +++ b/srclib/Box2D/Dynamics/b2Fixture.as @@ -0,0 +1,367 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + import Box2D.Collision.*; + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.Contacts.*; +use namespace b2internal; + + +/** + * A fixture is used to attach a shape to a body for collision detection. A fixture + * inherits its transform from its parent. Fixtures hold additional non-geometric data + * such as friction, collision filters, etc. + * Fixtures are created via b2Body::CreateFixture. + * @warning you cannot reuse fixtures. + */ +public class b2Fixture +{ + /** + * Get the type of the child shape. You can use this to down cast to the concrete shape. + * @return the shape type. + */ + public function GetType():int + { + return m_shape.GetType(); + } + + /** + * Get the child shape. You can modify the child shape, however you should not change the + * number of vertices because this will crash some collision caching mechanisms. + */ + public function GetShape():b2Shape + { + return m_shape; + } + + /** + * Set if this fixture is a sensor. + */ + public function SetSensor(sensor:Boolean):void + { + if ( m_isSensor == sensor) + return; + + m_isSensor = sensor; + + if (m_body == null) + return; + + var edge:b2ContactEdge = m_body.GetContactList(); + while (edge) + { + var contact:b2Contact = edge.contact; + var fixtureA:b2Fixture = contact.GetFixtureA(); + var fixtureB:b2Fixture = contact.GetFixtureB(); + if (fixtureA == this || fixtureB == this) + contact.SetSensor(fixtureA.IsSensor() || fixtureB.IsSensor()); + edge = edge.next; + } + + } + + /** + * Is this fixture a sensor (non-solid)? + * @return the true if the shape is a sensor. + */ + public function IsSensor():Boolean + { + return m_isSensor; + } + + /** + * Set the contact filtering data. This will not update contacts until the next time + * step when either parent body is active and awake. + */ + public function SetFilterData(filter:b2FilterData):void + { + m_filter = filter.Copy(); + + if (m_body) + return; + + var edge:b2ContactEdge = m_body.GetContactList(); + while (edge) + { + var contact:b2Contact = edge.contact; + var fixtureA:b2Fixture = contact.GetFixtureA(); + var fixtureB:b2Fixture = contact.GetFixtureB(); + if (fixtureA == this || fixtureB == this) + contact.FlagForFiltering(); + edge = edge.next; + } + } + + /** + * Get the contact filtering data. + */ + public function GetFilterData(): b2FilterData + { + return m_filter.Copy(); + } + + /** + * Get the parent body of this fixture. This is NULL if the fixture is not attached. + * @return the parent body. + */ + public function GetBody():b2Body + { + return m_body; + } + + /** + * Get the next fixture in the parent body's fixture list. + * @return the next shape. + */ + public function GetNext():b2Fixture + { + return m_next; + } + + /** + * Get the user data that was assigned in the fixture definition. Use this to + * store your application specific data. + */ + public function GetUserData():* + { + return m_userData; + } + + /** + * Set the user data. Use this to store your application specific data. + */ + public function SetUserData(data:*):void + { + m_userData = data; + } + + /** + * Test a point for containment in this fixture. + * @param xf the shape world transform. + * @param p a point in world coordinates. + */ + public function TestPoint(p:b2Vec2):Boolean + { + return m_shape.TestPoint(m_body.GetTransform(), p); + } + + /** + * Perform a ray cast against this shape. + * @param output the ray-cast results. + * @param input the ray-cast input parameters. + */ + public function RayCast(output:b2RayCastOutput, input:b2RayCastInput):Boolean + { + return m_shape.RayCast(output, input, m_body.GetTransform()); + } + + /** + * Get the mass data for this fixture. The mass data is based on the density and + * the shape. The rotational inertia is about the shape's origin. This operation may be expensive + * @param massData - this is a reference to a valid massData, if it is null a new b2MassData is allocated and then returned + * @note if the input is null then you must get the return value. + */ + public function GetMassData(massData:b2MassData = null):b2MassData + { + if ( massData == null ) + { + massData = new b2MassData(); + } + m_shape.ComputeMass(massData, m_density); + return massData; + } + + /** + * Set the density of this fixture. This will _not_ automatically adjust the mass + * of the body. You must call b2Body::ResetMassData to update the body's mass. + * @param density + */ + public function SetDensity(density:Number):void { + //b2Settings.b2Assert(b2Math.b2IsValid(density) && density >= 0.0); + m_density = density; + } + + /** + * Get the density of this fixture. + * @return density + */ + public function GetDensity():Number { + return m_density; + } + + /** + * Get the coefficient of friction. + */ + public function GetFriction():Number + { + return m_friction; + } + + /** + * Set the coefficient of friction. + */ + public function SetFriction(friction:Number):void + { + m_friction = friction; + } + + /** + * Get the coefficient of restitution. + */ + public function GetRestitution():Number + { + return m_restitution; + } + + /** + * Get the coefficient of restitution. + */ + public function SetRestitution(restitution:Number):void + { + m_restitution = restitution; + } + + /** + * Get the fixture's AABB. This AABB may be enlarge and/or stale. + * If you need a more accurate AABB, compute it using the shape and + * the body transform. + * @return + */ + public function GetAABB():b2AABB { + return m_aabb; + } + + /** + * @private + */ + public function b2Fixture() + { + m_aabb = new b2AABB(); + m_userData = null; + m_body = null; + m_next = null; + //m_proxyId = b2BroadPhase.e_nullProxy; + m_shape = null; + m_density = 0.0; + + m_friction = 0.0; + m_restitution = 0.0; + } + + /** + * the destructor cannot access the allocator (no destructor arguments allowed by C++). + * We need separation create/destroy functions from the constructor/destructor because + */ + b2internal function Create( body:b2Body, xf:b2Transform, def:b2FixtureDef):void + { + m_userData = def.userData; + m_friction = def.friction; + m_restitution = def.restitution; + + m_body = body; + m_next = null; + + m_filter = def.filter.Copy(); + + m_isSensor = def.isSensor; + + m_shape = def.shape.Copy(); + + m_density = def.density; + } + + /** + * the destructor cannot access the allocator (no destructor arguments allowed by C++). + * We need separation create/destroy functions from the constructor/destructor because + */ + b2internal function Destroy():void + { + // The proxy must be destroyed before calling this. + //b2Assert(m_proxyId == b2BroadPhase::e_nullProxy); + + // Free the child shape + m_shape = null; + } + + /** + * This supports body activation/deactivation. + */ + b2internal function CreateProxy(broadPhase:IBroadPhase, xf:b2Transform):void { + //b2Assert(m_proxyId == b2BroadPhase::e_nullProxy); + + // Create proxy in the broad-phase. + m_shape.ComputeAABB(m_aabb, xf); + m_proxy = broadPhase.CreateProxy(m_aabb, this); + } + + /** + * This supports body activation/deactivation. + */ + b2internal function DestroyProxy(broadPhase:IBroadPhase):void { + if (m_proxy == null) + { + return; + } + + // Destroy proxy in the broad-phase. + broadPhase.DestroyProxy(m_proxy); + m_proxy = null; + } + + b2internal function Synchronize(broadPhase:IBroadPhase, transform1:b2Transform, transform2:b2Transform):void + { + if (!m_proxy) + return; + + // Compute an AABB that ocvers the swept shape (may miss some rotation effect) + var aabb1:b2AABB = new b2AABB(); + var aabb2:b2AABB = new b2AABB(); + m_shape.ComputeAABB(aabb1, transform1); + m_shape.ComputeAABB(aabb2, transform2); + + m_aabb.Combine(aabb1, aabb2); + var displacement:b2Vec2 = b2Math.SubtractVV(transform2.position, transform1.position); + broadPhase.MoveProxy(m_proxy, m_aabb, displacement); + } + + private var m_massData:b2MassData; + + b2internal var m_aabb:b2AABB; + b2internal var m_density:Number; + b2internal var m_next:b2Fixture; + b2internal var m_body:b2Body; + b2internal var m_shape:b2Shape; + + b2internal var m_friction:Number; + b2internal var m_restitution:Number; + + b2internal var m_proxy:*; + b2internal var m_filter:b2FilterData = new b2FilterData(); + + b2internal var m_isSensor:Boolean; + + b2internal var m_userData:*; +}; + + + +} diff --git a/srclib/Box2D/Dynamics/b2FixtureDef.as b/srclib/Box2D/Dynamics/b2FixtureDef.as new file mode 100644 index 00000000..c35e94c8 --- /dev/null +++ b/srclib/Box2D/Dynamics/b2FixtureDef.as @@ -0,0 +1,88 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; +use namespace b2internal; + + +/** + * A fixture definition is used to create a fixture. This class defines an + * abstract fixture definition. You can reuse fixture definitions safely. + */ +public class b2FixtureDef +{ + /** + * The constructor sets the default fixture definition values. + */ + public function b2FixtureDef() + { + shape = null; + userData = null; + friction = 0.2; + restitution = 0.0; + density = 0.0; + filter.categoryBits = 0x0001; + filter.maskBits = 0xFFFF; + filter.groupIndex = 0; + isSensor = false; + } + + /** + * The shape, this must be set. The shape will be cloned, so you + * can create the shape on the stack. + */ + public var shape:b2Shape; + + /** + * Use this to store application specific fixture data. + */ + public var userData:*; + + /** + * The friction coefficient, usually in the range [0,1]. + */ + public var friction:Number; + + /** + * The restitution (elasticity) usually in the range [0,1]. + */ + public var restitution:Number; + + /** + * The density, usually in kg/m^2. + */ + public var density:Number; + + /** + * A sensor shape collects contact information but never generates a collision + * response. + */ + public var isSensor:Boolean; + + /** + * Contact filtering data. + */ + public var filter:b2FilterData = new b2FilterData(); +}; + + + +} diff --git a/srclib/Box2D/Dynamics/b2Island.as b/srclib/Box2D/Dynamics/b2Island.as new file mode 100644 index 00000000..d40d37c6 --- /dev/null +++ b/srclib/Box2D/Dynamics/b2Island.as @@ -0,0 +1,501 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.Contacts.*; + import Box2D.Dynamics.Joints.*; + +use namespace b2internal; + + +/* +Position Correction Notes +========================= +I tried the several algorithms for position correction of the 2D revolute joint. +I looked at these systems: +- simple pendulum (1m diameter sphere on massless 5m stick) with initial angular velocity of 100 rad/s. +- suspension bridge with 30 1m long planks of length 1m. +- multi-link chain with 30 1m long links. + +Here are the algorithms: + +Baumgarte - A fraction of the position error is added to the velocity error. There is no +separate position solver. + +Pseudo Velocities - After the velocity solver and position integration, +the position error, Jacobian, and effective mass are recomputed. Then +the velocity constraints are solved with pseudo velocities and a fraction +of the position error is added to the pseudo velocity error. The pseudo +velocities are initialized to zero and there is no warm-starting. After +the position solver, the pseudo velocities are added to the positions. +This is also called the First Order World method or the Position LCP method. + +Modified Nonlinear Gauss-Seidel (NGS) - Like Pseudo Velocities except the +position error is re-computed for each constraint and the positions are updated +after the constraint is solved. The radius vectors (aka Jacobians) are +re-computed too (otherwise the algorithm has horrible instability). The pseudo +velocity states are not needed because they are effectively zero at the beginning +of each iteration. Since we have the current position error, we allow the +iterations to terminate early if the error becomes smaller than b2_linearSlop. + +Full NGS or just NGS - Like Modified NGS except the effective mass are re-computed +each time a constraint is solved. + +Here are the results: +Baumgarte - this is the cheapest algorithm but it has some stability problems, +especially with the bridge. The chain links separate easily close to the root +and they jitter as they struggle to pull together. This is one of the most common +methods in the field. The big drawback is that the position correction artificially +affects the momentum, thus leading to instabilities and false bounce. I used a +bias factor of 0.2. A larger bias factor makes the bridge less stable, a smaller +factor makes joints and contacts more spongy. + +Pseudo Velocities - the is more stable than the Baumgarte method. The bridge is +stable. However, joints still separate with large angular velocities. Drag the +simple pendulum in a circle quickly and the joint will separate. The chain separates +easily and does not recover. I used a bias factor of 0.2. A larger value lead to +the bridge collapsing when a heavy cube drops on it. + +Modified NGS - this algorithm is better in some ways than Baumgarte and Pseudo +Velocities, but in other ways it is worse. The bridge and chain are much more +stable, but the simple pendulum goes unstable at high angular velocities. + +Full NGS - stable in all tests. The joints display good stiffness. The bridge +still sags, but this is better than infinite forces. + +Recommendations +Pseudo Velocities are not really worthwhile because the bridge and chain cannot +recover from joint separation. In other cases the benefit over Baumgarte is small. + +Modified NGS is not a robust method for the revolute joint due to the violent +instability seen in the simple pendulum. Perhaps it is viable with other constraint +types, especially scalar constraints where the effective mass is a scalar. + +This leaves Baumgarte and Full NGS. Baumgarte has small, but manageable instabilities +and is very fast. I don't think we can escape Baumgarte, especially in highly +demanding cases where high constraint fidelity is not needed. + +Full NGS is robust and easy on the eyes. I recommend this as an option for +higher fidelity simulation and certainly for suspension bridges and long chains. +Full NGS might be a good choice for ragdolls, especially motorized ragdolls where +joint separation can be problematic. The number of NGS iterations can be reduced +for better performance without harming robustness much. + +Each joint in a can be handled differently in the position solver. So I recommend +a system where the user can select the algorithm on a per joint basis. I would +probably default to the slower Full NGS and let the user select the faster +Baumgarte method in performance critical scenarios. +*/ + + +/** +* @private +*/ +public class b2Island +{ + + public function b2Island() + { + m_bodies = new Vector.(); + m_contacts = new Vector.(); + m_joints = new Vector.(); + } + + public function Initialize( + bodyCapacity:int, + contactCapacity:int, + jointCapacity:int, + allocator:*, + listener:b2ContactListener, + contactSolver:b2ContactSolver):void + { + var i:int; + + m_bodyCapacity = bodyCapacity; + m_contactCapacity = contactCapacity; + m_jointCapacity = jointCapacity; + m_bodyCount = 0; + m_contactCount = 0; + m_jointCount = 0; + + m_allocator = allocator; + m_listener = listener; + m_contactSolver = contactSolver; + + for (i = m_bodies.length; i < bodyCapacity; i++) + m_bodies[i] = null; + + for (i = m_contacts.length; i < contactCapacity; i++) + m_contacts[i] = null; + + for (i = m_joints.length; i < jointCapacity; i++) + m_joints[i] = null; + + } + //~b2Island(); + + public function Clear() : void + { + m_bodyCount = 0; + m_contactCount = 0; + m_jointCount = 0; + } + + public function Solve(step:b2TimeStep, gravity:b2Vec2, allowSleep:Boolean) : void + { + var i:int; + var j:int; + var b:b2Body; + var joint:b2Joint; + + // Integrate velocities and apply damping. + for (i = 0; i < m_bodyCount; ++i) + { + b = m_bodies[i]; + + if (b.GetType() != b2Body.b2_dynamicBody) + continue; + + // Integrate velocities. + //b.m_linearVelocity += step.dt * (gravity + b.m_invMass * b.m_force); + b.m_linearVelocity.x += step.dt * (gravity.x + b.m_invMass * b.m_force.x); + b.m_linearVelocity.y += step.dt * (gravity.y + b.m_invMass * b.m_force.y); + b.m_angularVelocity += step.dt * b.m_invI * b.m_torque; + + // Apply damping. + // ODE: dv/dt + c * v = 0 + // Solution: v(t) = v0 * exp(-c * t) + // Time step: v(t + dt) = v0 * exp(-c * (t + dt)) = v0 * exp(-c * t) * exp(-c * dt) = v * exp(-c * dt) + // v2 = exp(-c * dt) * v1 + // Taylor expansion: + // v2 = (1.0f - c * dt) * v1 + b.m_linearVelocity.Multiply( b2Math.Clamp(1.0 - step.dt * b.m_linearDamping, 0.0, 1.0) ); + b.m_angularVelocity *= b2Math.Clamp(1.0 - step.dt * b.m_angularDamping, 0.0, 1.0); + } + + m_contactSolver.Initialize(step, m_contacts, m_contactCount, m_allocator); + var contactSolver:b2ContactSolver = m_contactSolver; + + // Initialize velocity constraints. + contactSolver.InitVelocityConstraints(step); + + for (i = 0; i < m_jointCount; ++i) + { + joint = m_joints[i]; + joint.InitVelocityConstraints(step); + } + + // Solve velocity constraints. + for (i = 0; i < step.velocityIterations; ++i) + { + for (j = 0; j < m_jointCount; ++j) + { + joint = m_joints[j]; + joint.SolveVelocityConstraints(step); + } + + contactSolver.SolveVelocityConstraints(); + } + + // Post-solve (store impulses for warm starting). + for (i = 0; i < m_jointCount; ++i) + { + joint = m_joints[i]; + joint.FinalizeVelocityConstraints(); + } + contactSolver.FinalizeVelocityConstraints(); + + // Integrate positions. + for (i = 0; i < m_bodyCount; ++i) + { + b = m_bodies[i]; + + if (b.GetType() == b2Body.b2_staticBody) + continue; + + // Check for large velocities. + // b2Vec2 translation = step.dt * b.m_linearVelocity; + var translationX:Number = step.dt * b.m_linearVelocity.x; + var translationY:Number = step.dt * b.m_linearVelocity.y; + //if (b2Dot(translation, translation) > b2_maxTranslationSquared) + if ((translationX*translationX+translationY*translationY) > b2Settings.b2_maxTranslationSquared) + { + b.m_linearVelocity.Normalize(); + b.m_linearVelocity.x *= b2Settings.b2_maxTranslation * step.inv_dt; + b.m_linearVelocity.y *= b2Settings.b2_maxTranslation * step.inv_dt; + } + var rotation:Number = step.dt * b.m_angularVelocity; + if (rotation * rotation > b2Settings.b2_maxRotationSquared) + { + if (b.m_angularVelocity < 0.0) + { + b.m_angularVelocity = -b2Settings.b2_maxRotation * step.inv_dt; + } + else + { + b.m_angularVelocity = b2Settings.b2_maxRotation * step.inv_dt; + } + } + + // Store positions for continuous collision. + b.m_sweep.c0.SetV(b.m_sweep.c); + b.m_sweep.a0 = b.m_sweep.a; + + // Integrate + //b.m_sweep.c += step.dt * b.m_linearVelocity; + b.m_sweep.c.x += step.dt * b.m_linearVelocity.x; + b.m_sweep.c.y += step.dt * b.m_linearVelocity.y; + b.m_sweep.a += step.dt * b.m_angularVelocity; + + // Compute new transform + b.SynchronizeTransform(); + + // Note: shapes are synchronized later. + } + + // Iterate over constraints. + for (i = 0; i < step.positionIterations; ++i) + { + var contactsOkay:Boolean = contactSolver.SolvePositionConstraints(b2Settings.b2_contactBaumgarte); + + var jointsOkay:Boolean = true; + for (j = 0; j < m_jointCount; ++j) + { + joint = m_joints[j]; + var jointOkay:Boolean = joint.SolvePositionConstraints(b2Settings.b2_contactBaumgarte); + jointsOkay = jointsOkay && jointOkay; + } + + if (contactsOkay && jointsOkay) + { + break; + } + } + + Report(contactSolver.m_constraints); + + if (allowSleep){ + + var minSleepTime:Number = Number.MAX_VALUE; + + var linTolSqr:Number = b2Settings.b2_linearSleepTolerance * b2Settings.b2_linearSleepTolerance; + var angTolSqr:Number = b2Settings.b2_angularSleepTolerance * b2Settings.b2_angularSleepTolerance; + + for (i = 0; i < m_bodyCount; ++i) + { + b = m_bodies[i]; + if (b.GetType() == b2Body.b2_staticBody) + { + continue; + } + + if ((b.m_flags & b2Body.e_allowSleepFlag) == 0) + { + b.m_sleepTime = 0.0; + minSleepTime = 0.0; + } + + if ((b.m_flags & b2Body.e_allowSleepFlag) == 0 || + b.m_angularVelocity * b.m_angularVelocity > angTolSqr || + b2Math.Dot(b.m_linearVelocity, b.m_linearVelocity) > linTolSqr) + { + b.m_sleepTime = 0.0; + minSleepTime = 0.0; + } + else + { + b.m_sleepTime += step.dt; + minSleepTime = b2Math.Min(minSleepTime, b.m_sleepTime); + } + } + + if (minSleepTime >= b2Settings.b2_timeToSleep) + { + for (i = 0; i < m_bodyCount; ++i) + { + b = m_bodies[i]; + b.SetAwake(false); + } + } + } + } + + + public function SolveTOI(subStep:b2TimeStep) : void + { + var i:int; + var j:int; + m_contactSolver.Initialize(subStep, m_contacts, m_contactCount, m_allocator); + var contactSolver:b2ContactSolver = m_contactSolver; + + // No warm starting is needed for TOI events because warm + // starting impulses were applied in the discrete solver. + + // Warm starting for joints is off for now, but we need to + // call this function to compute Jacobians. + for (i = 0; i < m_jointCount;++i) + { + m_joints[i].InitVelocityConstraints(subStep); + } + + + // Solve velocity constraints. + for (i = 0; i < subStep.velocityIterations; ++i) + { + contactSolver.SolveVelocityConstraints(); + for (j = 0; j < m_jointCount;++j) + { + m_joints[j].SolveVelocityConstraints(subStep); + } + } + + // Don't store the TOI contact forces for warm starting + // because they can be quite large. + + // Integrate positions. + for (i = 0; i < m_bodyCount; ++i) + { + var b:b2Body = m_bodies[i]; + + if (b.GetType() == b2Body.b2_staticBody) + continue; + + // Check for large velocities. + // b2Vec2 translation = subStep.dt * b.m_linearVelocity; + var translationX:Number = subStep.dt * b.m_linearVelocity.x; + var translationY:Number = subStep.dt * b.m_linearVelocity.y; + //if (b2Dot(translation, translation) > b2_maxTranslationSquared) + if ((translationX*translationX+translationY*translationY) > b2Settings.b2_maxTranslationSquared) + { + b.m_linearVelocity.Normalize(); + b.m_linearVelocity.x *= b2Settings.b2_maxTranslation * subStep.inv_dt; + b.m_linearVelocity.y *= b2Settings.b2_maxTranslation * subStep.inv_dt; + } + + var rotation:Number = subStep.dt * b.m_angularVelocity; + if (rotation * rotation > b2Settings.b2_maxRotationSquared) + { + if (b.m_angularVelocity < 0.0) + { + b.m_angularVelocity = -b2Settings.b2_maxRotation * subStep.inv_dt; + } + else + { + b.m_angularVelocity = b2Settings.b2_maxRotation * subStep.inv_dt; + } + } + + // Store positions for continuous collision. + b.m_sweep.c0.SetV(b.m_sweep.c); + b.m_sweep.a0 = b.m_sweep.a; + + // Integrate + b.m_sweep.c.x += subStep.dt * b.m_linearVelocity.x; + b.m_sweep.c.y += subStep.dt * b.m_linearVelocity.y; + b.m_sweep.a += subStep.dt * b.m_angularVelocity; + + // Compute new transform + b.SynchronizeTransform(); + + // Note: shapes are synchronized later. + } + + // Solve position constraints. + var k_toiBaumgarte:Number = 0.75; + for (i = 0; i < subStep.positionIterations; ++i) + { + var contactsOkay:Boolean = contactSolver.SolvePositionConstraints(k_toiBaumgarte); + var jointsOkay:Boolean = true; + for (j = 0; j < m_jointCount;++j) + { + var jointOkay:Boolean = m_joints[j].SolvePositionConstraints(b2Settings.b2_contactBaumgarte); + jointsOkay = jointsOkay && jointOkay; + } + + if (contactsOkay && jointsOkay) + { + break; + } + } + Report(contactSolver.m_constraints); + } + + private static var s_impulse:b2ContactImpulse = new b2ContactImpulse(); + public function Report(constraints:Vector.) : void + { + if (m_listener == null) + { + return; + } + + for (var i:int = 0; i < m_contactCount; ++i) + { + var c:b2Contact = m_contacts[i]; + var cc:b2ContactConstraint = constraints[ i ]; + + for (var j:int = 0; j < cc.pointCount; ++j) + { + s_impulse.normalImpulses[j] = cc.points[j].normalImpulse; + s_impulse.tangentImpulses[j] = cc.points[j].tangentImpulse; + } + m_listener.PostSolve(c, s_impulse); + } + } + + + public function AddBody(body:b2Body) : void + { + //b2Settings.b2Assert(m_bodyCount < m_bodyCapacity); + body.m_islandIndex = m_bodyCount; + m_bodies[m_bodyCount++] = body; + } + + public function AddContact(contact:b2Contact) : void + { + //b2Settings.b2Assert(m_contactCount < m_contactCapacity); + m_contacts[m_contactCount++] = contact; + } + + public function AddJoint(joint:b2Joint) : void + { + //b2Settings.b2Assert(m_jointCount < m_jointCapacity); + m_joints[m_jointCount++] = joint; + } + + private var m_allocator:*; + private var m_listener:b2ContactListener; + private var m_contactSolver:b2ContactSolver; + + b2internal var m_bodies:Vector.; + b2internal var m_contacts:Vector.; + b2internal var m_joints:Vector.; + + b2internal var m_bodyCount:int; + b2internal var m_jointCount:int; + b2internal var m_contactCount:int; + + private var m_bodyCapacity:int; + b2internal var m_contactCapacity:int; + b2internal var m_jointCapacity:int; + +}; + +} diff --git a/srclib/Box2D/Dynamics/b2TimeStep.as b/srclib/Box2D/Dynamics/b2TimeStep.as new file mode 100644 index 00000000..3fedf0d4 --- /dev/null +++ b/srclib/Box2D/Dynamics/b2TimeStep.as @@ -0,0 +1,46 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + +/** +* @private +*/ +public class b2TimeStep +{ + public function b2TimeStep() {} + + public function Set(step:b2TimeStep):void + { + dt = step.dt; + inv_dt = step.inv_dt; + positionIterations = step.positionIterations; + velocityIterations = step.velocityIterations; + warmStarting = step.warmStarting; + } + public var dt:Number; // time step + public var inv_dt:Number; // inverse time step (0 if dt == 0). + public var dtRatio:Number; // dt * inv_dt0 + public var velocityIterations:int; + public var positionIterations:int; + public var warmStarting:Boolean; +}; + + +} \ No newline at end of file diff --git a/srclib/Box2D/Dynamics/b2World.as b/srclib/Box2D/Dynamics/b2World.as new file mode 100644 index 00000000..3a38516c --- /dev/null +++ b/srclib/Box2D/Dynamics/b2World.as @@ -0,0 +1,1588 @@ +/* +* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com +* +* This software is provided 'as-is', without any express or implied +* warranty. In no event will the authors be held liable for any damages +* arising from the use of this software. +* Permission is granted to anyone to use this software for any purpose, +* including commercial applications, and to alter it and redistribute it +* freely, subject to the following restrictions: +* 1. The origin of this software must not be misrepresented; you must not +* claim that you wrote the original software. If you use this software +* in a product, an acknowledgment in the product documentation would be +* appreciated but is not required. +* 2. Altered source versions must be plainly marked as such, and must not be +* misrepresented as being the original software. +* 3. This notice may not be removed or altered from any source distribution. +*/ + +package Box2D.Dynamics{ + + import Box2D.Collision.*; + import Box2D.Collision.Shapes.*; + import Box2D.Common.*; + import Box2D.Common.Math.*; + import Box2D.Dynamics.Contacts.*; + import Box2D.Dynamics.Controllers.b2Controller; + import Box2D.Dynamics.Controllers.b2ControllerEdge; + import Box2D.Dynamics.Joints.*; +use namespace b2internal; + + +/** +* The world class manages all physics entities, dynamic simulation, +* and asynchronous queries. +*/ +public class b2World +{ + + // Construct a world object. + /** + * @param gravity the world gravity vector. + * @param doSleep improve performance by not simulating inactive bodies. + */ + public function b2World(gravity:b2Vec2, doSleep:Boolean){ + + m_destructionListener = null; + m_debugDraw = null; + + m_bodyList = null; + m_contactList = null; + m_jointList = null; + m_controllerList = null; + + m_bodyCount = 0; + m_contactCount = 0; + m_jointCount = 0; + m_controllerCount = 0; + + m_warmStarting = true; + m_continuousPhysics = true; + + m_allowSleep = doSleep; + m_gravity = gravity; + + m_inv_dt0 = 0.0; + + m_contactManager.m_world = this; + + var bd:b2BodyDef = new b2BodyDef(); + m_groundBody = CreateBody(bd); + } + + /** + * Destruct the world. All physics entities are destroyed and all heap memory is released. + */ + //~b2World(); + + /** + * Register a destruction listener. + */ + public function SetDestructionListener(listener:b2DestructionListener) : void{ + m_destructionListener = listener; + } + + /** + * Register a contact filter to provide specific control over collision. + * Otherwise the default filter is used (b2_defaultFilter). + */ + public function SetContactFilter(filter:b2ContactFilter) : void{ + m_contactManager.m_contactFilter = filter; + } + + /** + * Register a contact event listener + */ + public function SetContactListener(listener:b2ContactListener) : void{ + m_contactManager.m_contactListener = listener; + } + + /** + * Register a routine for debug drawing. The debug draw functions are called + * inside the b2World::Step method, so make sure your renderer is ready to + * consume draw commands when you call Step(). + */ + public function SetDebugDraw(debugDraw:b2DebugDraw) : void{ + m_debugDraw = debugDraw; + } + + /** + * Use the given object as a broadphase. + * The old broadphase will not be cleanly emptied. + * @warning It is not recommended you call this except immediately after constructing the world. + * @warning This function is locked during callbacks. + */ + public function SetBroadPhase(broadPhase:IBroadPhase) : void { + var oldBroadPhase:IBroadPhase = m_contactManager.m_broadPhase; + m_contactManager.m_broadPhase = broadPhase; + for (var b:b2Body = m_bodyList; b; b = b.m_next) + { + for (var f:b2Fixture = b.m_fixtureList; f; f = f.m_next) + { + f.m_proxy = broadPhase.CreateProxy(oldBroadPhase.GetFatAABB(f.m_proxy), f); + } + } + } + + /** + * Perform validation of internal data structures. + */ + public function Validate() : void + { + m_contactManager.m_broadPhase.Validate(); + } + + /** + * Get the number of broad-phase proxies. + */ + public function GetProxyCount() : int + { + return m_contactManager.m_broadPhase.GetProxyCount(); + } + + /** + * Create a rigid body given a definition. No reference to the definition + * is retained. + * @warning This function is locked during callbacks. + */ + public function CreateBody(def:b2BodyDef) : b2Body{ + + //b2Settings.b2Assert(m_lock == false); + if (IsLocked() == true) + { + return null; + } + + //void* mem = m_blockAllocator.Allocate(sizeof(b2Body)); + var b:b2Body = new b2Body(def, this); + + // Add to world doubly linked list. + b.m_prev = null; + b.m_next = m_bodyList; + if (m_bodyList) + { + m_bodyList.m_prev = b; + } + m_bodyList = b; + ++m_bodyCount; + + return b; + + } + + /** + * Destroy a rigid body given a definition. No reference to the definition + * is retained. This function is locked during callbacks. + * @warning This automatically deletes all associated shapes and joints. + * @warning This function is locked during callbacks. + */ + public function DestroyBody(b:b2Body) : void{ + + //b2Settings.b2Assert(m_bodyCount > 0); + //b2Settings.b2Assert(m_lock == false); + if (IsLocked() == true) + { + return; + } + + // Delete the attached joints. + var jn:b2JointEdge = b.m_jointList; + while (jn) + { + var jn0:b2JointEdge = jn; + jn = jn.next; + + if (m_destructionListener) + { + m_destructionListener.SayGoodbyeJoint(jn0.joint); + } + + DestroyJoint(jn0.joint); + } + + // Detach controllers attached to this body + var coe:b2ControllerEdge = b.m_controllerList; + while (coe) + { + var coe0:b2ControllerEdge = coe; + coe = coe.nextController; + coe0.controller.RemoveBody(b); + } + + // Delete the attached contacts. + var ce:b2ContactEdge = b.m_contactList; + while (ce) + { + var ce0:b2ContactEdge = ce; + ce = ce.next; + m_contactManager.Destroy(ce0.contact); + } + b.m_contactList = null; + + // Delete the attached fixtures. This destroys broad-phase + // proxies. + var f:b2Fixture = b.m_fixtureList; + while (f) + { + var f0:b2Fixture = f; + f = f.m_next; + + if (m_destructionListener) + { + m_destructionListener.SayGoodbyeFixture(f0); + } + + f0.DestroyProxy(m_contactManager.m_broadPhase); + f0.Destroy(); + //f0->~b2Fixture(); + //m_blockAllocator.Free(f0, sizeof(b2Fixture)); + + } + b.m_fixtureList = null; + b.m_fixtureCount = 0; + + // Remove world body list. + if (b.m_prev) + { + b.m_prev.m_next = b.m_next; + } + + if (b.m_next) + { + b.m_next.m_prev = b.m_prev; + } + + if (b == m_bodyList) + { + m_bodyList = b.m_next; + } + + --m_bodyCount; + //b->~b2Body(); + //m_blockAllocator.Free(b, sizeof(b2Body)); + + } + + /** + * Create a joint to constrain bodies together. No reference to the definition + * is retained. This may cause the connected bodies to cease colliding. + * @warning This function is locked during callbacks. + */ + public function CreateJoint(def:b2JointDef) : b2Joint{ + + //b2Settings.b2Assert(m_lock == false); + + var j:b2Joint = b2Joint.Create(def, null); + + // Connect to the world list. + j.m_prev = null; + j.m_next = m_jointList; + if (m_jointList) + { + m_jointList.m_prev = j; + } + m_jointList = j; + ++m_jointCount; + + // Connect to the bodies' doubly linked lists. + j.m_edgeA.joint = j; + j.m_edgeA.other = j.m_bodyB; + j.m_edgeA.prev = null; + j.m_edgeA.next = j.m_bodyA.m_jointList; + if (j.m_bodyA.m_jointList) j.m_bodyA.m_jointList.prev = j.m_edgeA; + j.m_bodyA.m_jointList = j.m_edgeA; + + j.m_edgeB.joint = j; + j.m_edgeB.other = j.m_bodyA; + j.m_edgeB.prev = null; + j.m_edgeB.next = j.m_bodyB.m_jointList; + if (j.m_bodyB.m_jointList) j.m_bodyB.m_jointList.prev = j.m_edgeB; + j.m_bodyB.m_jointList = j.m_edgeB; + + var bodyA:b2Body = def.bodyA; + var bodyB:b2Body = def.bodyB; + + // If the joint prevents collisions, then flag any contacts for filtering. + if (def.collideConnected == false ) + { + var edge:b2ContactEdge = bodyB.GetContactList(); + while (edge) + { + if (edge.other == bodyA) + { + // Flag the contact for filtering at the next time step (where either + // body is awake). + edge.contact.FlagForFiltering(); + } + + edge = edge.next; + } + } + + // Note: creating a joint doesn't wake the bodies. + + return j; + + } + + /** + * Destroy a joint. This may cause the connected bodies to begin colliding. + * @warning This function is locked during callbacks. + */ + public function DestroyJoint(j:b2Joint) : void{ + + //b2Settings.b2Assert(m_lock == false); + + var collideConnected:Boolean = j.m_collideConnected; + + // Remove from the doubly linked list. + if (j.m_prev) + { + j.m_prev.m_next = j.m_next; + } + + if (j.m_next) + { + j.m_next.m_prev = j.m_prev; + } + + if (j == m_jointList) + { + m_jointList = j.m_next; + } + + // Disconnect from island graph. + var bodyA:b2Body = j.m_bodyA; + var bodyB:b2Body = j.m_bodyB; + + // Wake up connected bodies. + bodyA.SetAwake(true); + bodyB.SetAwake(true); + + // Remove from body 1. + if (j.m_edgeA.prev) + { + j.m_edgeA.prev.next = j.m_edgeA.next; + } + + if (j.m_edgeA.next) + { + j.m_edgeA.next.prev = j.m_edgeA.prev; + } + + if (j.m_edgeA == bodyA.m_jointList) + { + bodyA.m_jointList = j.m_edgeA.next; + } + + j.m_edgeA.prev = null; + j.m_edgeA.next = null; + + // Remove from body 2 + if (j.m_edgeB.prev) + { + j.m_edgeB.prev.next = j.m_edgeB.next; + } + + if (j.m_edgeB.next) + { + j.m_edgeB.next.prev = j.m_edgeB.prev; + } + + if (j.m_edgeB == bodyB.m_jointList) + { + bodyB.m_jointList = j.m_edgeB.next; + } + + j.m_edgeB.prev = null; + j.m_edgeB.next = null; + + b2Joint.Destroy(j, null); + + //b2Settings.b2Assert(m_jointCount > 0); + --m_jointCount; + + // If the joint prevents collisions, then flag any contacts for filtering. + if (collideConnected == false) + { + var edge:b2ContactEdge = bodyB.GetContactList(); + while (edge) + { + if (edge.other == bodyA) + { + // Flag the contact for filtering at the next time step (where either + // body is awake). + edge.contact.FlagForFiltering(); + } + + edge = edge.next; + } + } + + } + + /** + * Add a controller to the world list + */ + public function AddController(c:b2Controller) : b2Controller + { + c.m_next = m_controllerList; + c.m_prev = null; + m_controllerList = c; + + c.m_world = this; + + m_controllerCount++; + + return c; + } + + public function RemoveController(c:b2Controller) : void + { + //TODO: Remove bodies from controller + if (c.m_prev) + c.m_prev.m_next = c.m_next; + if (c.m_next) + c.m_next.m_prev = c.m_prev; + if (m_controllerList == c) + m_controllerList = c.m_next; + + m_controllerCount--; + } + + public function CreateController(controller:b2Controller):b2Controller + { + if (controller.m_world != this) + throw new Error("Controller can only be a member of one world"); + + controller.m_next = m_controllerList; + controller.m_prev = null; + if (m_controllerList) + m_controllerList.m_prev = controller; + m_controllerList = controller; + ++m_controllerCount; + + controller.m_world = this; + + return controller; + } + + public function DestroyController(controller:b2Controller):void + { + //b2Settings.b2Assert(m_controllerCount > 0); + controller.Clear(); + if (controller.m_next) + controller.m_next.m_prev = controller.m_prev; + if (controller.m_prev) + controller.m_prev.m_next = controller.m_next; + if (controller == m_controllerList) + m_controllerList = controller.m_next; + --m_controllerCount; + } + + /** + * Enable/disable warm starting. For testing. + */ + public function SetWarmStarting(flag: Boolean) : void { m_warmStarting = flag; } + + /** + * Enable/disable continuous physics. For testing. + */ + public function SetContinuousPhysics(flag: Boolean) : void { m_continuousPhysics = flag; } + + /** + * Get the number of bodies. + */ + public function GetBodyCount() : int + { + return m_bodyCount; + } + + /** + * Get the number of joints. + */ + public function GetJointCount() : int + { + return m_jointCount; + } + + /** + * Get the number of contacts (each may have 0 or more contact points). + */ + public function GetContactCount() : int + { + return m_contactCount; + } + + /** + * Change the global gravity vector. + */ + public function SetGravity(gravity: b2Vec2): void + { + m_gravity = gravity; + } + + /** + * Get the global gravity vector. + */ + public function GetGravity():b2Vec2{ + return m_gravity; + } + + /** + * The world provides a single static ground body with no collision shapes. + * You can use this to simplify the creation of joints and static shapes. + */ + public function GetGroundBody() : b2Body{ + return m_groundBody; + } + + private static var s_timestep2:b2TimeStep = new b2TimeStep(); + /** + * Take a time step. This performs collision detection, integration, + * and constraint solution. + * @param timeStep the amount of time to simulate, this should not vary. + * @param velocityIterations for the velocity constraint solver. + * @param positionIterations for the position constraint solver. + */ + public function Step(dt:Number, velocityIterations:int, positionIterations:int) : void{ + if (m_flags & e_newFixture) + { + m_contactManager.FindNewContacts(); + m_flags &= ~e_newFixture; + } + + m_flags |= e_locked; + + var step:b2TimeStep = s_timestep2; + step.dt = dt; + step.velocityIterations = velocityIterations; + step.positionIterations = positionIterations; + if (dt > 0.0) + { + step.inv_dt = 1.0 / dt; + } + else + { + step.inv_dt = 0.0; + } + + step.dtRatio = m_inv_dt0 * dt; + + step.warmStarting = m_warmStarting; + + // Update contacts. + m_contactManager.Collide(); + + // Integrate velocities, solve velocity constraints, and integrate positions. + if (step.dt > 0.0) + { + Solve(step); + } + + // Handle TOI events. + if (m_continuousPhysics && step.dt > 0.0) + { + SolveTOI(step); + } + + if (step.dt > 0.0) + { + m_inv_dt0 = step.inv_dt; + } + m_flags &= ~e_locked; + } + + /** + * Call this after you are done with time steps to clear the forces. You normally + * call this after each call to Step, unless you are performing sub-steps. + */ + public function ClearForces() : void + { + for (var body:b2Body = m_bodyList; body; body = body.m_next) + { + body.m_force.SetZero(); + body.m_torque = 0.0; + } + } + + static private var s_xf:b2Transform = new b2Transform(); + /** + * Call this to draw shapes and other debug draw data. + */ + public function DrawDebugData() : void{ + + if (m_debugDraw == null) + { + return; + } + + m_debugDraw.m_sprite.graphics.clear(); + + var flags:uint = m_debugDraw.GetFlags(); + + var i:int; + var b:b2Body; + var f:b2Fixture; + var s:b2Shape; + var j:b2Joint; + var bp:IBroadPhase; + var invQ:b2Vec2 = new b2Vec2; + var x1:b2Vec2 = new b2Vec2; + var x2:b2Vec2 = new b2Vec2; + var xf:b2Transform; + var b1:b2AABB = new b2AABB(); + var b2:b2AABB = new b2AABB(); + var vs:Array = [new b2Vec2(), new b2Vec2(), new b2Vec2(), new b2Vec2()]; + + // Store color here and reuse, to reduce allocations + var color:b2Color = new b2Color(0, 0, 0); + + if (flags & b2DebugDraw.e_shapeBit) + { + for (b = m_bodyList; b; b = b.m_next) + { + xf = b.m_xf; + for (f = b.GetFixtureList(); f; f = f.m_next) + { + s = f.GetShape(); + if (b.IsActive() == false) + { + color.Set(0.5, 0.5, 0.3); + DrawShape(s, xf, color); + } + else if (b.GetType() == b2Body.b2_staticBody) + { + color.Set(0.5, 0.9, 0.5); + DrawShape(s, xf, color); + } + else if (b.GetType() == b2Body.b2_kinematicBody) + { + color.Set(0.5, 0.5, 0.9); + DrawShape(s, xf, color); + } + else if (b.IsAwake() == false) + { + color.Set(0.6, 0.6, 0.6); + DrawShape(s, xf, color); + } + else + { + color.Set(0.9, 0.7, 0.7); + DrawShape(s, xf, color); + } + } + } + } + + if (flags & b2DebugDraw.e_jointBit) + { + for (j = m_jointList; j; j = j.m_next) + { + DrawJoint(j); + } + } + + if (flags & b2DebugDraw.e_controllerBit) + { + for (var c:b2Controller = m_controllerList; c; c = c.m_next) + { + c.Draw(m_debugDraw); + } + } + + if (flags & b2DebugDraw.e_pairBit) + { + color.Set(0.3, 0.9, 0.9); + for (var contact:b2Contact = m_contactManager.m_contactList; contact; contact = contact.GetNext()) + { + var fixtureA:b2Fixture = contact.GetFixtureA(); + var fixtureB:b2Fixture = contact.GetFixtureB(); + + var cA:b2Vec2 = fixtureA.GetAABB().GetCenter(); + var cB:b2Vec2 = fixtureB.GetAABB().GetCenter(); + + m_debugDraw.DrawSegment(cA, cB, color); + } + } + + if (flags & b2DebugDraw.e_aabbBit) + { + bp = m_contactManager.m_broadPhase; + + vs = [new b2Vec2(),new b2Vec2(),new b2Vec2(),new b2Vec2()]; + + for (b= m_bodyList; b; b = b.GetNext()) + { + if (b.IsActive() == false) + { + continue; + } + for (f = b.GetFixtureList(); f; f = f.GetNext()) + { + var aabb:b2AABB = bp.GetFatAABB(f.m_proxy); + vs[0].Set(aabb.lowerBound.x, aabb.lowerBound.y); + vs[1].Set(aabb.upperBound.x, aabb.lowerBound.y); + vs[2].Set(aabb.upperBound.x, aabb.upperBound.y); + vs[3].Set(aabb.lowerBound.x, aabb.upperBound.y); + + m_debugDraw.DrawPolygon(vs, 4, color); + } + } + } + + if (flags & b2DebugDraw.e_centerOfMassBit) + { + for (b = m_bodyList; b; b = b.m_next) + { + xf = s_xf; + xf.R = b.m_xf.R; + xf.position = b.GetWorldCenter(); + m_debugDraw.DrawTransform(xf); + } + } + } + + /** + * Query the world for all fixtures that potentially overlap the + * provided AABB. + * @param callback a user implemented callback class. It should match signature + * function Callback(fixture:b2Fixture):Boolean + * Return true to continue to the next fixture. + * @param aabb the query box. + */ + public function QueryAABB(callback:Function, aabb:b2AABB):void + { + var broadPhase:IBroadPhase = m_contactManager.m_broadPhase; + function WorldQueryWrapper(proxy:*):Boolean + { + return callback(broadPhase.GetUserData(proxy)); + } + broadPhase.Query(WorldQueryWrapper, aabb); + } + /** + * Query the world for all fixtures that precisely overlap the + * provided transformed shape. + * @param callback a user implemented callback class. It should match signature + * function Callback(fixture:b2Fixture):Boolean + * Return true to continue to the next fixture. + * @asonly + */ + public function QueryShape(callback:Function, shape:b2Shape, transform:b2Transform = null):void + { + if (transform == null) + { + transform = new b2Transform(); + transform.SetIdentity(); + } + var broadPhase:IBroadPhase = m_contactManager.m_broadPhase; + function WorldQueryWrapper(proxy:*):Boolean + { + var fixture:b2Fixture = broadPhase.GetUserData(proxy) as b2Fixture + if(b2Shape.TestOverlap(shape, transform, fixture.GetShape(), fixture.GetBody().GetTransform())) + return callback(fixture); + return true; + } + var aabb:b2AABB = new b2AABB(); + shape.ComputeAABB(aabb, transform); + broadPhase.Query(WorldQueryWrapper, aabb); + } + + /** + * Query the world for all fixtures that contain a point. + * @param callback a user implemented callback class. It should match signature + * function Callback(fixture:b2Fixture):Boolean + * Return true to continue to the next fixture. + * @asonly + */ + public function QueryPoint(callback:Function, p:b2Vec2):void + { + var broadPhase:IBroadPhase = m_contactManager.m_broadPhase; + function WorldQueryWrapper(proxy:*):Boolean + { + var fixture:b2Fixture = broadPhase.GetUserData(proxy) as b2Fixture + if(fixture.TestPoint(p)) + return callback(fixture); + return true; + } + // Make a small box. + var aabb:b2AABB = new b2AABB(); + aabb.lowerBound.Set(p.x - b2Settings.b2_linearSlop, p.y - b2Settings.b2_linearSlop); + aabb.upperBound.Set(p.x + b2Settings.b2_linearSlop, p.y + b2Settings.b2_linearSlop); + broadPhase.Query(WorldQueryWrapper, aabb); + } + + /** + * Ray-cast the world for all fixtures in the path of the ray. Your callback + * Controls whether you get the closest point, any point, or n-points + * The ray-cast ignores shapes that contain the starting point + * @param callback A callback function which must be of signature: + * function Callback(fixture:b2Fixture, // The fixture hit by the ray + * point:b2Vec2, // The point of initial intersection + * normal:b2Vec2, // The normal vector at the point of intersection + * fraction:Number // The fractional length along the ray of the intersection + * ):Number + * + * Callback should return the new length of the ray as a fraction of the original length. + * By returning 0, you immediately terminate. + * By returning 1, you continue with the original ray. + * By returning the current fraction, you proceed to find the closest point. + * @param point1 the ray starting point + * @param point2 the ray ending point + */ + public function RayCast(callback:Function, point1:b2Vec2, point2:b2Vec2):void + { + var broadPhase:IBroadPhase = m_contactManager.m_broadPhase; + var output:b2RayCastOutput = new b2RayCastOutput; + function RayCastWrapper(input:b2RayCastInput, proxy:*):Number + { + var userData:* = broadPhase.GetUserData(proxy); + var fixture:b2Fixture = userData as b2Fixture; + var hit:Boolean = fixture.RayCast(output, input); + if (hit) + { + var fraction:Number = output.fraction; + var point:b2Vec2 = new b2Vec2( + (1.0 - fraction) * point1.x + fraction * point2.x, + (1.0 - fraction) * point1.y + fraction * point2.y); + return callback(fixture, point, output.normal, fraction); + } + return input.maxFraction; + } + var input:b2RayCastInput = new b2RayCastInput(point1, point2); + broadPhase.RayCast(RayCastWrapper, input); + } + + public function RayCastOne(point1:b2Vec2, point2:b2Vec2):b2Fixture + { + var result:b2Fixture; + function RayCastOneWrapper(fixture:b2Fixture, point:b2Vec2, normal:b2Vec2, fraction:Number):Number + { + result = fixture; + return fraction; + } + RayCast(RayCastOneWrapper, point1, point2); + return result; + } + + public function RayCastAll(point1:b2Vec2, point2:b2Vec2):Vector. + { + var result:Vector. = new Vector.(); + function RayCastAllWrapper(fixture:b2Fixture, point:b2Vec2, normal:b2Vec2, fraction:Number):Number + { + result[result.length] = fixture; + return 1; + } + RayCast(RayCastAllWrapper, point1, point2); + return result; + } + + /** + * Get the world body list. With the returned body, use b2Body::GetNext to get + * the next body in the world list. A NULL body indicates the end of the list. + * @return the head of the world body list. + */ + public function GetBodyList() : b2Body{ + return m_bodyList; + } + + /** + * Get the world joint list. With the returned joint, use b2Joint::GetNext to get + * the next joint in the world list. A NULL joint indicates the end of the list. + * @return the head of the world joint list. + */ + public function GetJointList() : b2Joint{ + return m_jointList; + } + + /** + * Get the world contact list. With the returned contact, use b2Contact::GetNext to get + * the next contact in the world list. A NULL contact indicates the end of the list. + * @return the head of the world contact list. + * @warning contacts are + */ + public function GetContactList():b2Contact + { + return m_contactList; + } + + /** + * Is the world locked (in the middle of a time step). + */ + public function IsLocked():Boolean + { + return (m_flags & e_locked) > 0; + } + + //--------------- Internals Below ------------------- + // Internal yet public to make life easier. + + // Find islands, integrate and solve constraints, solve position constraints + private var s_stack:Vector. = new Vector.(); + b2internal function Solve(step:b2TimeStep) : void{ + var b:b2Body; + + // Step all controllers + for(var controller:b2Controller= m_controllerList;controller;controller=controller.m_next) + { + controller.Step(step); + } + + // Size the island for the worst case. + var island:b2Island = m_island; + island.Initialize(m_bodyCount, m_contactCount, m_jointCount, null, m_contactManager.m_contactListener, m_contactSolver); + + // Clear all the island flags. + for (b = m_bodyList; b; b = b.m_next) + { + b.m_flags &= ~b2Body.e_islandFlag; + } + for (var c:b2Contact = m_contactList; c; c = c.m_next) + { + c.m_flags &= ~b2Contact.e_islandFlag; + } + for (var j:b2Joint = m_jointList; j; j = j.m_next) + { + j.m_islandFlag = false; + } + + // Build and simulate all awake islands. + var stackSize:int = m_bodyCount; + //b2Body** stack = (b2Body**)m_stackAllocator.Allocate(stackSize * sizeof(b2Body*)); + var stack:Vector. = s_stack; + for (var seed:b2Body = m_bodyList; seed; seed = seed.m_next) + { + if (seed.m_flags & b2Body.e_islandFlag ) + { + continue; + } + + if (seed.IsAwake() == false || seed.IsActive() == false) + { + continue; + } + + // The seed can be dynamic or kinematic. + if (seed.GetType() == b2Body.b2_staticBody) + { + continue; + } + + // Reset island and stack. + island.Clear(); + var stackCount:int = 0; + stack[stackCount++] = seed; + seed.m_flags |= b2Body.e_islandFlag; + + // Perform a depth first search (DFS) on the constraint graph. + while (stackCount > 0) + { + // Grab the next body off the stack and add it to the island. + b = stack[--stackCount]; + //b2Assert(b.IsActive() == true); + island.AddBody(b); + + // Make sure the body is awake. + if (b.IsAwake() == false) + { + b.SetAwake(true); + } + + // To keep islands as small as possible, we don't + // propagate islands across static bodies. + if (b.GetType() == b2Body.b2_staticBody) + { + continue; + } + + var other:b2Body; + // Search all contacts connected to this body. + for (var ce:b2ContactEdge = b.m_contactList; ce; ce = ce.next) + { + // Has this contact already been added to an island? + if (ce.contact.m_flags & b2Contact.e_islandFlag) + { + continue; + } + + // Is this contact solid and touching? + if (ce.contact.IsSensor() == true || + ce.contact.IsEnabled() == false || + ce.contact.IsTouching() == false) + { + continue; + } + + island.AddContact(ce.contact); + ce.contact.m_flags |= b2Contact.e_islandFlag; + + //var other:b2Body = ce.other; + other = ce.other; + + // Was the other body already added to this island? + if (other.m_flags & b2Body.e_islandFlag) + { + continue; + } + + //b2Settings.b2Assert(stackCount < stackSize); + stack[stackCount++] = other; + other.m_flags |= b2Body.e_islandFlag; + } + + // Search all joints connect to this body. + for (var jn:b2JointEdge = b.m_jointList; jn; jn = jn.next) + { + if (jn.joint.m_islandFlag == true) + { + continue; + } + + other = jn.other; + + // Don't simulate joints connected to inactive bodies. + if (other.IsActive() == false) + { + continue; + } + + island.AddJoint(jn.joint); + jn.joint.m_islandFlag = true; + + if (other.m_flags & b2Body.e_islandFlag) + { + continue; + } + + //b2Settings.b2Assert(stackCount < stackSize); + stack[stackCount++] = other; + other.m_flags |= b2Body.e_islandFlag; + } + } + island.Solve(step, m_gravity, m_allowSleep); + + // Post solve cleanup. + for (var i:int = 0; i < island.m_bodyCount; ++i) + { + // Allow static bodies to participate in other islands. + b = island.m_bodies[i]; + if (b.GetType() == b2Body.b2_staticBody) + { + b.m_flags &= ~b2Body.e_islandFlag; + } + } + } + + //m_stackAllocator.Free(stack); + for (i = 0; i < stack.length;++i) + { + if (!stack[i]) break; + stack[i] = null; + } + + // Synchronize fixutres, check for out of range bodies. + for (b = m_bodyList; b; b = b.m_next) + { + if (b.IsAwake() == false || b.IsActive() == false) + { + continue; + } + + if (b.GetType() == b2Body.b2_staticBody) + { + continue; + } + + // Update fixtures (for broad-phase). + b.SynchronizeFixtures(); + } + + // Look for new contacts. + m_contactManager.FindNewContacts(); + + } + + private static var s_backupA:b2Sweep = new b2Sweep(); + private static var s_backupB:b2Sweep = new b2Sweep(); + private static var s_timestep:b2TimeStep = new b2TimeStep(); + private static var s_queue:Vector. = new Vector.(); + // Find TOI contacts and solve them. + b2internal function SolveTOI(step:b2TimeStep) : void{ + + var b:b2Body; + var fA:b2Fixture; + var fB:b2Fixture; + var bA:b2Body; + var bB:b2Body; + var cEdge:b2ContactEdge; + var j:b2Joint; + + // Reserve an island and a queue for TOI island solution. + var island:b2Island = m_island; + island.Initialize(m_bodyCount, b2Settings.b2_maxTOIContactsPerIsland, b2Settings.b2_maxTOIJointsPerIsland, null, m_contactManager.m_contactListener, m_contactSolver); + + //Simple one pass queue + //Relies on the fact that we're only making one pass + //through and each body can only be pushed/popped one. + //To push: + // queue[queueStart+queueSize++] = newElement; + //To pop: + // poppedElement = queue[queueStart++]; + // --queueSize; + + var queue:Vector. = s_queue; + + for (b = m_bodyList; b; b = b.m_next) + { + b.m_flags &= ~b2Body.e_islandFlag; + b.m_sweep.t0 = 0.0; + } + + var c:b2Contact; + for (c = m_contactList; c; c = c.m_next) + { + // Invalidate TOI + c.m_flags &= ~(b2Contact.e_toiFlag | b2Contact.e_islandFlag); + } + + for (j = m_jointList; j; j = j.m_next) + { + j.m_islandFlag = false; + } + + // Find TOI events and solve them. + for (;;) + { + // Find the first TOI. + var minContact:b2Contact = null; + var minTOI:Number = 1.0; + + for (c = m_contactList; c; c = c.m_next) + { + // Can this contact generate a solid TOI contact? + if (c.IsSensor() == true || + c.IsEnabled() == false || + c.IsContinuous() == false) + { + continue; + } + + // TODO_ERIN keep a counter on the contact, only respond to M TOIs per contact. + + var toi:Number = 1.0; + if (c.m_flags & b2Contact.e_toiFlag) + { + // This contact has a valid cached TOI. + toi = c.m_toi; + } + else + { + // Compute the TOI for this contact. + fA = c.m_fixtureA; + fB = c.m_fixtureB; + bA = fA.m_body; + bB = fB.m_body; + + if ((bA.GetType() != b2Body.b2_dynamicBody || bA.IsAwake() == false) && + (bB.GetType() != b2Body.b2_dynamicBody || bB.IsAwake() == false)) + { + continue; + } + + // Put the sweeps onto the same time interval. + var t0:Number = bA.m_sweep.t0; + + if (bA.m_sweep.t0 < bB.m_sweep.t0) + { + t0 = bB.m_sweep.t0; + bA.m_sweep.Advance(t0); + } + else if (bB.m_sweep.t0 < bA.m_sweep.t0) + { + t0 = bA.m_sweep.t0; + bB.m_sweep.Advance(t0); + } + + //b2Settings.b2Assert(t0 < 1.0f); + + // Compute the time of impact. + toi = c.ComputeTOI(bA.m_sweep, bB.m_sweep); + b2Settings.b2Assert(0.0 <= toi && toi <= 1.0); + + // If the TOI is in range ... + if (toi > 0.0 && toi < 1.0) + { + // Interpolate on the actual range. + //toi = Math.min((1.0 - toi) * t0 + toi, 1.0); + toi = (1.0 - toi) * t0 + toi; + if (toi > 1) toi = 1; + } + + + c.m_toi = toi; + c.m_flags |= b2Contact.e_toiFlag; + } + + if (Number.MIN_VALUE < toi && toi < minTOI) + { + // This is the minimum TOI found so far. + minContact = c; + minTOI = toi; + } + } + + if (minContact == null || 1.0 - 100.0 * Number.MIN_VALUE < minTOI) + { + // No more TOI events. Done! + break; + } + + // Advance the bodies to the TOI. + fA = minContact.m_fixtureA; + fB = minContact.m_fixtureB; + bA = fA.m_body; + bB = fB.m_body; + s_backupA.Set(bA.m_sweep); + s_backupB.Set(bB.m_sweep); + bA.Advance(minTOI); + bB.Advance(minTOI); + + // The TOI contact likely has some new contact points. + minContact.Update(m_contactManager.m_contactListener); + minContact.m_flags &= ~b2Contact.e_toiFlag; + + // Is the contact solid? + if (minContact.IsSensor() == true || + minContact.IsEnabled() == false) + { + // Restore the sweeps + bA.m_sweep.Set(s_backupA); + bB.m_sweep.Set(s_backupB); + bA.SynchronizeTransform(); + bB.SynchronizeTransform(); + continue; + } + + // Did numerical issues prevent;,ontact pointjrom being generated + if (minContact.IsTouching() == false) + { + // Give up on this TOI + continue; + } + + // Build the TOI island. We need a dynamic seed. + var seed:b2Body = bA; + if (seed.GetType() != b2Body.b2_dynamicBody) + { + seed = bB; + } + + // Reset island and queue. + island.Clear(); + var queueStart:int = 0; //start index for queue + var queueSize:int = 0; //elements in queue + queue[queueStart + queueSize++] = seed; + seed.m_flags |= b2Body.e_islandFlag; + + // Perform a breadth first search (BFS) on the contact graph. + while (queueSize > 0) + { + // Grab the next body off the stack and add it to the island. + b = queue[queueStart++]; + --queueSize; + + island.AddBody(b); + + // Make sure the body is awake. + if (b.IsAwake() == false) + { + b.SetAwake(true); + } + + // To keep islands as small as possible, we don't + // propagate islands across static or kinematic bodies. + if (b.GetType() != b2Body.b2_dynamicBody) + { + continue; + } + + // Search all contacts connected to this body. + for (cEdge = b.m_contactList; cEdge; cEdge = cEdge.next) + { + // Does the TOI island still have space for contacts? + if (island.m_contactCount == island.m_contactCapacity) + { + break; + } + + // Has this contact already been added to an island? + if (cEdge.contact.m_flags & b2Contact.e_islandFlag) + { + continue; + } + + // Skip sperate, sensor, or disabled contacts. + if (cEdge.contact.IsSensor() == true || + cEdge.contact.IsEnabled() == false || + cEdge.contact.IsTouching() == false) + { + continue; + } + + island.AddContact(cEdge.contact); + cEdge.contact.m_flags |= b2Contact.e_islandFlag; + + // Update other body. + var other:b2Body = cEdge.other; + + // Was the other body already added to this island? + if (other.m_flags & b2Body.e_islandFlag) + { + continue; + } + + // Synchronize the connected body. + if (other.GetType() != b2Body.b2_staticBody) + { + other.Advance(minTOI); + other.SetAwake(true); + } + + //b2Settings.b2Assert(queueStart + queueSize < queueCapacity); + queue[queueStart + queueSize] = other; + ++queueSize; + other.m_flags |= b2Body.e_islandFlag; + } + + for (var jEdge:b2JointEdge = b.m_jointList; jEdge; jEdge = jEdge.next) + { + if (island.m_jointCount == island.m_jointCapacity) + continue; + + if (jEdge.joint.m_islandFlag == true) + continue; + + other = jEdge.other; + if (other.IsActive() == false) + { + continue; + } + + island.AddJoint(jEdge.joint); + jEdge.joint.m_islandFlag = true; + + if (other.m_flags & b2Body.e_islandFlag) + continue; + + // Synchronize the connected body. + if (other.GetType() != b2Body.b2_staticBody) + { + other.Advance(minTOI); + other.SetAwake(true); + } + + //b2Settings.b2Assert(queueStart + queueSize < queueCapacity); + queue[queueStart + queueSize] = other; + ++queueSize; + other.m_flags |= b2Body.e_islandFlag; + } + } + + var subStep:b2TimeStep = s_timestep; + subStep.warmStarting = false; + subStep.dt = (1.0 - minTOI) * step.dt; + subStep.inv_dt = 1.0 / subStep.dt; + subStep.dtRatio = 0.0; + subStep.velocityIterations = step.velocityIterations; + subStep.positionIterations = step.positionIterations; + + island.SolveTOI(subStep); + + var i:int; + // Post solve cleanup. + for (i = 0; i < island.m_bodyCount; ++i) + { + // Allow bodies to participate in future TOI islands. + b = island.m_bodies[i]; + b.m_flags &= ~b2Body.e_islandFlag; + + if (b.IsAwake() == false) + { + continue; + } + + if (b.GetType() != b2Body.b2_dynamicBody) + { + continue; + } + + // Update fixtures (for broad-phase). + b.SynchronizeFixtures(); + + // Invalidate all contact TOIs associated with this body. Some of these + // may not be in the island because they were not touching. + for (cEdge = b.m_contactList; cEdge; cEdge = cEdge.next) + { + cEdge.contact.m_flags &= ~b2Contact.e_toiFlag; + } + } + + for (i = 0; i < island.m_contactCount; ++i) + { + // Allow contacts to participate in future TOI islands. + c = island.m_contacts[i]; + c.m_flags &= ~(b2Contact.e_toiFlag | b2Contact.e_islandFlag); + } + + for (i = 0; i < island.m_jointCount;++i) + { + // Allow joints to participate in future TOI islands + j = island.m_joints[i]; + j.m_islandFlag = false; + } + + // Commit fixture proxy movements to the broad-phase so that new contacts are created. + // Also, some contacts can be destroyed. + m_contactManager.FindNewContacts(); + } + + //m_stackAllocator.Free(queue); + } + + static private var s_jointColor:b2Color = new b2Color(0.5, 0.8, 0.8); + // + b2internal function DrawJoint(joint:b2Joint) : void{ + + var b1:b2Body = joint.GetBodyA(); + var b2:b2Body = joint.GetBodyB(); + var xf1:b2Transform = b1.m_xf; + var xf2:b2Transform = b2.m_xf; + var x1:b2Vec2 = xf1.position; + var x2:b2Vec2 = xf2.position; + var p1:b2Vec2 = joint.GetAnchorA(); + var p2:b2Vec2 = joint.GetAnchorB(); + + //b2Color color(0.5f, 0.8f, 0.8f); + var color:b2Color = s_jointColor; + + switch (joint.m_type) + { + case b2Joint.e_distanceJoint: + m_debugDraw.DrawSegment(p1, p2, color); + break; + + case b2Joint.e_pulleyJoint: + { + var pulley:b2PulleyJoint = (joint as b2PulleyJoint); + var s1:b2Vec2 = pulley.GetGroundAnchorA(); + var s2:b2Vec2 = pulley.GetGroundAnchorB(); + m_debugDraw.DrawSegment(s1, p1, color); + m_debugDraw.DrawSegment(s2, p2, color); + m_debugDraw.DrawSegment(s1, s2, color); + } + break; + + case b2Joint.e_mouseJoint: + m_debugDraw.DrawSegment(p1, p2, color); + break; + + default: + if (b1 != m_groundBody) + m_debugDraw.DrawSegment(x1, p1, color); + m_debugDraw.DrawSegment(p1, p2, color); + if (b2 != m_groundBody) + m_debugDraw.DrawSegment(x2, p2, color); + } + } + + b2internal function DrawShape(shape:b2Shape, xf:b2Transform, color:b2Color) : void{ + + switch (shape.m_type) + { + case b2Shape.e_circleShape: + { + var circle:b2CircleShape = (shape as b2CircleShape); + + var center:b2Vec2 = b2Math.MulX(xf, circle.m_p); + var radius:Number = circle.m_radius; + var axis:b2Vec2 = xf.R.col1; + + m_debugDraw.DrawSolidCircle(center, radius, axis, color); + } + break; + + case b2Shape.e_polygonShape: + { + var i:int; + var poly:b2PolygonShape = (shape as b2PolygonShape); + var vertexCount:int = poly.GetVertexCount(); + var localVertices:Vector. = poly.GetVertices(); + + var vertices:Vector. = new Vector.(vertexCount); + + for (i = 0; i < vertexCount; ++i) + { + vertices[i] = b2Math.MulX(xf, localVertices[i]); + } + + m_debugDraw.DrawSolidPolygon(vertices, vertexCount, color); + } + break; + + case b2Shape.e_edgeShape: + { + var edge: b2EdgeShape = shape as b2EdgeShape; + + m_debugDraw.DrawSegment(b2Math.MulX(xf, edge.GetVertex1()), b2Math.MulX(xf, edge.GetVertex2()), color); + + } + break; + } + } + + b2internal var m_flags:int; + + b2internal var m_contactManager:b2ContactManager = new b2ContactManager(); + + // These two are stored purely for efficiency purposes, they don't maintain + // any data outside of a call to Step + private var m_contactSolver:b2ContactSolver = new b2ContactSolver(); + private var m_island:b2Island = new b2Island(); + + b2internal var m_bodyList:b2Body; + private var m_jointList:b2Joint; + + b2internal var m_contactList:b2Contact; + + private var m_bodyCount:int; + b2internal var m_contactCount:int; + private var m_jointCount:int; + private var m_controllerList:b2Controller; + private var m_controllerCount:int; + + private var m_gravity:b2Vec2; + private var m_allowSleep:Boolean; + + b2internal var m_groundBody:b2Body; + + private var m_destructionListener:b2DestructionListener; + private var m_debugDraw:b2DebugDraw; + + // This is used to compute the time step ratio to support a variable time step. + private var m_inv_dt0:Number; + + // This is for debugging the solver. + static private var m_warmStarting:Boolean; + + // This is for debugging the solver. + static private var m_continuousPhysics:Boolean; + + // m_flags + public static const e_newFixture:int = 0x0001; + public static const e_locked:int = 0x0002; + +}; + + + +} diff --git a/srclib/dragonBones/Armature.as b/srclib/dragonBones/Armature.as new file mode 100644 index 00000000..a92c9529 --- /dev/null +++ b/srclib/dragonBones/Armature.as @@ -0,0 +1,720 @@ +package dragonBones { + + import dragonBones.animation.Animation; + import dragonBones.animation.AnimationState; + import dragonBones.animation.TimelineState; + import dragonBones.core.IArmature; + import dragonBones.core.dragonBones_internal; + import dragonBones.events.ArmatureEvent; + import dragonBones.events.FrameEvent; + import dragonBones.events.SoundEvent; + import dragonBones.events.SoundEventManager; + import dragonBones.objects.ArmatureData; + import dragonBones.objects.DragonBonesData; + import dragonBones.objects.Frame; + import dragonBones.objects.SkinData; + import dragonBones.objects.SlotData; + + import flash.events.Event; + import flash.events.EventDispatcher; + + use namespace dragonBones_internal; + + /** + * Dispatched when slot's zOrder changed + */ + [Event(name="zOrderUpdated", type="dragonBones.events.ArmatureEvent")] + + /** + * Dispatched when an animation state begins fade in (Even if fade in time is 0) + */ + [Event(name="fadeIn", type="dragonBones.events.AnimationEvent")] + + /** + * Dispatched when an animation state begins fade out (Even if fade out time is 0) + */ + [Event(name="fadeOut", type="dragonBones.events.AnimationEvent")] + + /** + * Dispatched when an animation state start to play(AnimationState may play when fade in start or end. It is controllable). + */ + [Event(name="start", type="dragonBones.events.AnimationEvent")] + + /** + * Dispatched when an animation state play complete (if playtimes equals to 0 means loop forever. Then this Event will not be triggered) + */ + [Event(name="complete", type="dragonBones.events.AnimationEvent")] + + /** + * Dispatched when an animation state complete a loop. + */ + [Event(name="loopComplete", type="dragonBones.events.AnimationEvent")] + + /** + * Dispatched when an animation state fade in complete. + */ + [Event(name="fadeInComplete", type="dragonBones.events.AnimationEvent")] + + /** + * Dispatched when an animation state fade out complete. + */ + [Event(name="fadeOutComplete", type="dragonBones.events.AnimationEvent")] + + /** + * Dispatched when an animation state enter a frame with animation frame event. + */ + [Event(name="animationFrameEvent", type="dragonBones.events.FrameEvent")] + + /** + * Dispatched when an bone enter a frame with animation frame event. + */ + [Event(name="boneFrameEvent", type="dragonBones.events.FrameEvent")] + + public class Armature extends EventDispatcher implements IArmature + { + dragonBones_internal var __dragonBonesData:DragonBonesData; + + + /** + * The instance dispatch sound event. + */ + private static const _soundManager:SoundEventManager = SoundEventManager.getInstance(); + + /** + * The name should be same with ArmatureData's name + */ + public var name:String; + + /** + * An object that can contain any user extra data. + */ + public var userData:Object; + + /** @private Set it to true when slot's zorder changed*/ + dragonBones_internal var _slotsZOrderChanged:Boolean; + + /** @private Store event needed to dispatch in current frame. When advanceTime execute complete, dispath them.*/ + dragonBones_internal var _eventList:Vector.; + + + /** @private Store slots based on slots' zOrder*/ + protected var _slotList:Vector.; + + /** @private Store bones based on bones' hierarchy (From root to leaf)*/ + protected var _boneList:Vector.; + + private var _delayDispose:Boolean; + private var _lockDispose:Boolean; + + /** @private */ + dragonBones_internal var _armatureData:ArmatureData; + /** + * ArmatureData. + * @see dragonBones.objects.ArmatureData. + */ + public function get armatureData():ArmatureData + { + return _armatureData; + } + + /** @private */ + protected var _display:Object; + /** + * Armature's display object. It's instance type depends on render engine. For example "flash.display.DisplayObject" or "startling.display.DisplayObject" + */ + public function get display():Object + { + return _display; + } + + /** @private */ + protected var _animation:Animation; + /** + * An Animation instance + * @see dragonBones.animation.Animation + */ + public function get animation():Animation + { + return _animation; + } + + /** + * save more skinLists + */ + dragonBones_internal var _skinLists:Object; + /** + * Creates a Armature blank instance. + * @param Instance type of this object varies from flash.display.DisplayObject to startling.display.DisplayObject and subclasses. + * @see #display + */ + public function Armature(display:Object) + { + super(this); + _display = display; + + _animation = new Animation(this); + + _slotsZOrderChanged = false; + + _slotList = new Vector.; + _slotList.fixed = true; + _boneList = new Vector.; + _boneList.fixed = true; + _eventList = new Vector.; + _skinLists = { }; + _delayDispose = false; + _lockDispose = false; + + _armatureData = null; + } + + /** + * Cleans up any resources used by this instance. + */ + public function dispose():void + { + _delayDispose = true; + if(!_animation || _lockDispose) + { + return; + } + + userData = null; + + _animation.dispose(); + var i:int = _slotList.length; + while(i --) + { + _slotList[i].dispose(); + } + i = _boneList.length; + while(i --) + { + _boneList[i].dispose(); + } + + _slotList.fixed = false; + _slotList.length = 0; + _boneList.fixed = false; + _boneList.length = 0; + _eventList.length = 0; + + _armatureData = null; + _animation = null; + _slotList = null; + _boneList = null; + _eventList = null; + + //_display = null; + } + + /** + * Force update bones and slots. (When bone's animation play complete, it will not update) + */ + public function invalidUpdate(boneName:String = null):void + { + if(boneName) + { + var bone:Bone = getBone(boneName); + if(bone) + { + bone.invalidUpdate(); + } + } + else + { + var i:int = _boneList.length; + while(i --) + { + _boneList[i].invalidUpdate(); + } + } + } + + /** + * Update the animation using this method typically in an ENTERFRAME Event or with a Timer. + * @param The amount of second to move the playhead ahead. + */ + public function advanceTime(passedTime:Number):void + { + _lockDispose = true; + + _animation.advanceTime(passedTime); + + passedTime *= _animation.timeScale; //_animation's time scale will impact childArmature + + var isFading:Boolean = _animation._isFading; + var i:int = _boneList.length; + while(i --) + { + var bone:Bone = _boneList[i]; + bone.update(isFading); + } + + i = _slotList.length; + while(i --) + { + var slot:Slot = _slotList[i]; + slot.update(); + if(slot._isShowDisplay) + { + var childArmature:Armature = slot.childArmature; + if(childArmature) + { + childArmature.advanceTime(passedTime); + } + } + } + + if(_slotsZOrderChanged) + { + updateSlotsZOrder(); + + if(this.hasEventListener(ArmatureEvent.Z_ORDER_UPDATED)) + { + this.dispatchEvent(new ArmatureEvent(ArmatureEvent.Z_ORDER_UPDATED)); + } + } + + if(_eventList.length) + { + for each(var event:Event in _eventList) + { + this.dispatchEvent(event); + } + _eventList.length = 0; + } + + _lockDispose = false; + if(_delayDispose) + { + dispose(); + } + } + + public function resetAnimation():void + { + animation.stop(); + animation.resetAnimationStateList(); + + for each(var boneItem:Bone in _boneList) + { + boneItem.removeAllStates(); + } + } + + /** + * Get all Slot instance associated with this armature. + * @param if return Vector copy + * @return A Vector.<Slot> instance. + * @see dragonBones.Slot + */ + public function getSlots(returnCopy:Boolean = true):Vector. + { + return returnCopy?_slotList.concat():_slotList; + } + + /** + * Retrieves a Slot by name + * @param The name of the Bone to retrieve. + * @return A Slot instance or null if no Slot with that name exist. + * @see dragonBones.Slot + */ + public function getSlot(slotName:String):Slot + { + for each(var slot:Slot in _slotList) + { + if(slot.name == slotName) + { + return slot; + } + } + return null; + } + + /** + * Gets the Slot associated with this DisplayObject. + * @param Instance type of this object varies from flash.display.DisplayObject to startling.display.DisplayObject and subclasses. + * @return A Slot instance or null if no Slot with that DisplayObject exist. + * @see dragonBones.Slot + */ + public function getSlotByDisplay(displayObj:Object):Slot + { + if(displayObj) + { + for each(var slot:Slot in _slotList) + { + if(slot.display == displayObj) + { + return slot; + } + } + } + return null; + } + + /** + * Add a slot to a bone as child. + * @param slot A Slot instance + * @param boneName bone name + * @see dragonBones.core.DBObject + */ + public function addSlot(slot:Slot, boneName:String):void + { + var bone:Bone = getBone(boneName); + if (bone) + { + bone.addSlot(slot); + } + else + { + throw new ArgumentError(); + } + } + + /** + * Remove a Slot instance from this Armature instance. + * @param The Slot instance to remove. + * @see dragonBones.Slot + */ + public function removeSlot(slot:Slot):void + { + if(!slot || slot.armature != this) + { + throw new ArgumentError(); + } + + slot.parent.removeSlot(slot); + } + + /** + * Remove a Slot instance from this Armature instance. + * @param The name of the Slot instance to remove. + * @see dragonBones.Slot + */ + public function removeSlotByName(slotName:String):Slot + { + var slot:Slot = getSlot(slotName); + if(slot) + { + removeSlot(slot); + } + return slot; + } + + /** + * Get all Bone instance associated with this armature. + * @param if return Vector copy + * @return A Vector.<Bone> instance. + * @see dragonBones.Bone + */ + public function getBones(returnCopy:Boolean = true):Vector. + { + return returnCopy?_boneList.concat():_boneList; + } + + /** + * Retrieves a Bone by name + * @param The name of the Bone to retrieve. + * @return A Bone instance or null if no Bone with that name exist. + * @see dragonBones.Bone + */ + public function getBone(boneName:String):Bone + { + for each(var bone:Bone in _boneList) + { + if(bone.name == boneName) + { + return bone; + } + } + return null; + } + + /** + * Gets the Bone associated with this DisplayObject. + * @param Instance type of this object varies from flash.display.DisplayObject to startling.display.DisplayObject and subclasses. + * @return A Bone instance or null if no Bone with that DisplayObject exist.. + * @see dragonBones.Bone + */ + public function getBoneByDisplay(display:Object):Bone + { + var slot:Slot = getSlotByDisplay(display); + return slot?slot.parent:null; + } + + /** + * Add a Bone instance to this Armature instance. + * @param A Bone instance. + * @param (optional) The parent's name of this Bone instance. + * @see dragonBones.Bone + */ + public function addBone(bone:Bone, parentName:String = null, updateLater:Boolean = false):void + { + var parentBone:Bone; + if(parentName) + { + parentBone = getBone(parentName); + if (!parentBone) + { + throw new ArgumentError(); + } + } + + if(parentBone) + { + parentBone.addChildBone(bone, updateLater); + } + else + { + if(bone.parent) + { + bone.parent.removeChildBone(bone, updateLater); + } + bone.setArmature(this); + if(!updateLater) + { + updateAnimationAfterBoneListChanged(); + } + } + } + + /** + * Remove a Bone instance from this Armature instance. + * @param The Bone instance to remove. + * @see dragonBones.Bone + */ + public function removeBone(bone:Bone, updateLater:Boolean = false):void + { + if(!bone || bone.armature != this) + { + throw new ArgumentError(); + } + + if(bone.parent) + { + bone.parent.removeChildBone(bone, updateLater); + } + else + { + bone.setArmature(null); + if(!updateLater) + { + updateAnimationAfterBoneListChanged(false); + } + } + } + + /** + * Remove a Bone instance from this Armature instance. + * @param The name of the Bone instance to remove. + * @see dragonBones.Bone + */ + public function removeBoneByName(boneName:String):Bone + { + var bone:Bone = getBone(boneName); + if(bone) + { + removeBone(bone); + } + return bone; + } + + /** @private */ + dragonBones_internal function addBoneToBoneList(bone:Bone):void + { + if(_boneList.indexOf(bone) < 0) + { + _boneList.fixed = false; + _boneList[_boneList.length] = bone; + _boneList.fixed = true; + } + } + + /** @private */ + dragonBones_internal function removeBoneFromBoneList(bone:Bone):void + { + var index:int = _boneList.indexOf(bone); + if(index >= 0) + { + _boneList.fixed = false; + _boneList.splice(index, 1); + _boneList.fixed = true; + } + } + + /** @private */ + dragonBones_internal function addSlotToSlotList(slot:Slot):void + { + if(_slotList.indexOf(slot) < 0) + { + _slotList.fixed = false; + _slotList[_slotList.length] = slot; + _slotList.fixed = true; + } + } + + /** @private */ + dragonBones_internal function removeSlotFromSlotList(slot:Slot):void + { + var index:int = _slotList.indexOf(slot); + if(index >= 0) + { + _slotList.fixed = false; + _slotList.splice(index, 1); + _slotList.fixed = true; + } + } + + /** + * Sort all slots based on zOrder + */ + public function updateSlotsZOrder():void + { + _slotList.fixed = false; + _slotList.sort(sortSlot); + _slotList.fixed = true; + var i:int = _slotList.length; + while(i --) + { + var slot:Slot = _slotList[i]; + if(slot._isShowDisplay) + { + //_display 实际上是container, 这个方法就是把原来的显示对象放到container中的第一个 + slot.addDisplayToContainer(_display); + } + } + + _slotsZOrderChanged = false; + } + + dragonBones_internal function updateAnimationAfterBoneListChanged(ifNeedSortBoneList:Boolean = true):void + { + if(ifNeedSortBoneList) + { + sortBoneList(); + } + _animation.updateAnimationStates(); + } + + private function sortBoneList():void + { + var i:int = _boneList.length; + if(i == 0) + { + return; + } + var helpArray:Array = []; + while(i --) + { + var level:int = 0; + var bone:Bone = _boneList[i]; + var boneParent:Bone = bone; + while(boneParent) + { + level ++; + boneParent = boneParent.parent; + } + helpArray[i] = [level, bone]; + } + + helpArray.sortOn("0", Array.NUMERIC|Array.DESCENDING); + + i = helpArray.length; + + _boneList.fixed = false; + while(i --) + { + _boneList[i] = helpArray[i][1]; + } + _boneList.fixed = true; + + helpArray.length = 0; + } + + /** @private When AnimationState enter a key frame, call this func*/ + dragonBones_internal function arriveAtFrame(frame:Frame, timelineState:TimelineState, animationState:AnimationState, isCross:Boolean):void + { + if(frame.event && this.hasEventListener(FrameEvent.ANIMATION_FRAME_EVENT)) + { + var frameEvent:FrameEvent = new FrameEvent(FrameEvent.ANIMATION_FRAME_EVENT); + frameEvent.animationState = animationState; + frameEvent.frameLabel = frame.event; + _eventList.push(frameEvent); + } + + if(frame.sound && _soundManager.hasEventListener(SoundEvent.SOUND)) + { + var soundEvent:SoundEvent = new SoundEvent(SoundEvent.SOUND); + soundEvent.armature = this; + soundEvent.animationState = animationState; + soundEvent.sound = frame.sound; + _soundManager.dispatchEvent(soundEvent); + } + + //[TODO]currently there is only gotoAndPlay belongs to frame action. In future, there will be more. + //后续会扩展更多的action,目前只有gotoAndPlay的含义 + if(frame.action) + { + if(animationState.displayControl) + { + animation.gotoAndPlay(frame.action); + } + } + } + + private function sortSlot(slot1:Slot, slot2:Slot):int + { + return slot1.zOrder < slot2.zOrder?1: -1; + } + + public function addSkinList(skinName:String, list:Object):void + { + if (!skinName) + { + skinName = "default"; + } + if (!_skinLists[skinName]) + { + _skinLists[skinName] = list; + } + } + + public function changeSkin(skinName:String):void + { + var skinData:SkinData = armatureData.getSkinData(skinName); + if(!skinData || !_skinLists[skinName]) + { + return; + } + armatureData.setSkinData(skinName); + var displayList:Array = []; + var slotDataList:Vector. = armatureData.slotDataList; + var slotData:SlotData; + var slot:Slot; + var bone:Bone; + for(var i:int = 0; i < slotDataList.length; i++) + { + + slotData = slotDataList[i]; + displayList = _skinLists[skinName][slotData.name]; + bone = getBone(slotData.parent); + if(!bone || !displayList) + { + continue; + } + + slot = getSlot(slotData.name); + slot.initWithSlotData(slotData); + + slot.displayList = displayList; + slot.changeDisplay(0); + } + } + + public function getAnimation():Object + { + return _animation; + } + } +} diff --git a/srclib/dragonBones/Bone.as b/srclib/dragonBones/Bone.as new file mode 100644 index 00000000..da4682c4 --- /dev/null +++ b/srclib/dragonBones/Bone.as @@ -0,0 +1,665 @@ +package dragonBones { + + import dragonBones.animation.AnimationState; + import dragonBones.animation.TimelineState; + import dragonBones.core.DBObject; + import dragonBones.core.dragonBones_internal; + import dragonBones.events.FrameEvent; + import dragonBones.events.SoundEvent; + import dragonBones.events.SoundEventManager; + import dragonBones.objects.BoneData; + import dragonBones.objects.DBTransform; + import dragonBones.objects.Frame; + import dragonBones.utils.TransformUtil; + + import flash.geom.Matrix; + import flash.geom.Point; + + use namespace dragonBones_internal; + + public class Bone extends DBObject + { + /** + * The instance dispatch sound event. + */ + private static const _soundManager:SoundEventManager = SoundEventManager.getInstance(); + + public static function initWithBoneData(boneData:BoneData):Bone + { + var outputBone:Bone = new Bone(); + + outputBone.name = boneData.name; + outputBone.inheritRotation = boneData.inheritRotation; + outputBone.inheritScale = boneData.inheritScale; + outputBone.origin.copy(boneData.transform); + + return outputBone; + } + + public var applyOffsetTranslationToChild:Boolean = true; + + public var applyOffsetRotationToChild:Boolean = true; + + public var applyOffsetScaleToChild:Boolean = false; + + /** + * AnimationState that slots belong to the bone will be controlled by. + * Sometimes, we want slots controlled by a spedific animation state when animation is doing mix or addition. + */ + public var displayController:String; + + /** @private */ + protected var _boneList:Vector.; + + /** @private */ + protected var _slotList:Vector.; + + /** @private */ + protected var _timelineStateList:Vector.; + + /** @private */ + dragonBones_internal var _tween:DBTransform; + + /** @private */ + dragonBones_internal var _tweenPivot:Point; + + /** @private */ + dragonBones_internal var _needUpdate:int; + + /** @private */ + //dragonBones_internal var _isColorChanged:Boolean; + + /** @private */ + dragonBones_internal var _globalTransformForChild:DBTransform; + /** @private */ + dragonBones_internal var _globalTransformMatrixForChild:Matrix; + + private var _tempGlobalTransformForChild:DBTransform; + private var _tempGlobalTransformMatrixForChild:Matrix; + + public function Bone() + { + super(); + + _tween = new DBTransform(); + _tweenPivot = new Point(); + _tween.scaleX = _tween.scaleY = 1; + + _boneList = new Vector.; + _boneList.fixed = true; + _slotList = new Vector.; + _slotList.fixed = true; + _timelineStateList = new Vector.; + + _needUpdate = 2; + //_isColorChanged = false; + } + + /** + * @inheritDoc + */ + override public function dispose():void + { + if(!_boneList) + { + return; + } + + super.dispose(); + var i:int = _boneList.length; + while(i --) + { + _boneList[i].dispose(); + } + + i = _slotList.length; + while(i --) + { + _slotList[i].dispose(); + } + + _boneList.fixed = false; + _boneList.length = 0; + _slotList.fixed = false; + _slotList.length = 0; + _timelineStateList.length = 0; + + _tween = null; + _tweenPivot = null; + _boneList = null; + _slotList = null; + _timelineStateList = null; + } + +//骨架装配 + /** + * If contains some bone or slot + * @param Slot or Bone instance + * @return Boolean + * @see dragonBones.core.DBObject + */ + public function contains(child:DBObject):Boolean + { + if(!child) + { + throw new ArgumentError(); + } + if(child == this) + { + return false; + } + var ancestor:DBObject = child; + while(!(ancestor == this || ancestor == null)) + { + ancestor = ancestor.parent; + } + return ancestor == this; + } + + /** + * Add a bone as child + * @param a Bone instance + * @see dragonBones.core.DBObject + */ + public function addChildBone(childBone:Bone, updateLater:Boolean = false):void + { + if(!childBone) + { + throw new ArgumentError(); + } + + if(childBone == this || childBone.contains(this)) + { + throw new ArgumentError("An Bone cannot be added as a child to itself or one of its children (or children's children, etc.)"); + } + + if(childBone.parent == this) + { + return; + } + + if(childBone.parent) + { + childBone.parent.removeChildBone(childBone); + } + + _boneList.fixed = false; + _boneList[_boneList.length] = childBone; + _boneList.fixed = true; + childBone.setParent(this); + childBone.setArmature(_armature); + + if(_armature && !updateLater) + { + _armature.updateAnimationAfterBoneListChanged(); + } + } + + /** + * remove a child bone + * @param a Bone instance + * @see dragonBones.core.DBObject + */ + public function removeChildBone(childBone:Bone, updateLater:Boolean = false):void + { + if(!childBone) + { + throw new ArgumentError(); + } + + var index:int = _boneList.indexOf(childBone); + if(index < 0) + { + throw new ArgumentError(); + } + + _boneList.fixed = false; + _boneList.splice(index, 1); + _boneList.fixed = true; + childBone.setParent(null); + childBone.setArmature(null); + + if(_armature && !updateLater) + { + _armature.updateAnimationAfterBoneListChanged(false); + } + } + + /** + * Add a slot as child + * @param a Slot instance + * @see dragonBones.core.DBObject + */ + public function addSlot(childSlot:Slot):void + { + if(!childSlot) + { + throw new ArgumentError(); + } + + if(childSlot.parent) + { + childSlot.parent.removeSlot(childSlot); + } + + _slotList.fixed = false; + _slotList[_slotList.length] = childSlot; + _slotList.fixed = true; + childSlot.setParent(this); + childSlot.setArmature(this._armature); + } + + /** + * remove a child slot + * @param a Slot instance + * @see dragonBones.core.DBObject + */ + public function removeSlot(childSlot:Slot):void + { + if(!childSlot) + { + throw new ArgumentError(); + } + + var index:int = _slotList.indexOf(childSlot); + if(index < 0) + { + throw new ArgumentError(); + } + + _slotList.fixed = false; + _slotList.splice(index, 1); + _slotList.fixed = true; + childSlot.setParent(null); + childSlot.setArmature(null); + } + + /** @private */ + override dragonBones_internal function setArmature(value:Armature):void + { + if(_armature == value) + { + return; + } + if(_armature) + { + _armature.removeBoneFromBoneList(this); + _armature.updateAnimationAfterBoneListChanged(false); + } + _armature = value; + if(_armature) + { + _armature.addBoneToBoneList(this); + } + + var i:int = _boneList.length; + while(i --) + { + _boneList[i].setArmature(this._armature); + } + + i = _slotList.length; + while(i --) + { + _slotList[i].setArmature(this._armature); + } + } + + /** + * Get all Bone instance associated with this bone. + * @return A Vector.<Slot> instance. + * @see dragonBones.Slot + */ + public function getBones(returnCopy:Boolean = true):Vector. + { + return returnCopy?_boneList.concat():_boneList; + } + + /** + * Get all Slot instance associated with this bone. + * @return A Vector.<Slot> instance. + * @see dragonBones.Slot + */ + public function getSlots(returnCopy:Boolean = true):Vector. + { + return returnCopy?_slotList.concat():_slotList; + } + +//动画 + /** + * Force update the bone in next frame even if the bone is not moving. + */ + public function invalidUpdate():void + { + _needUpdate = 2; + } + + override protected function calculateRelativeParentTransform():void + { + _global.scaleX = this._origin.scaleX * _tween.scaleX * this._offset.scaleX; + _global.scaleY = this._origin.scaleY * _tween.scaleY * this._offset.scaleY; + _global.skewX = this._origin.skewX + _tween.skewX + this._offset.skewX; + _global.skewY = this._origin.skewY + _tween.skewY + this._offset.skewY; + _global.x = this._origin.x + _tween.x + this._offset.x; + _global.y = this._origin.y + _tween.y + this._offset.y; + } + + /** @private */ + dragonBones_internal function update(needUpdate:Boolean = false):void + { + _needUpdate --; + if(needUpdate || _needUpdate > 0 || (this._parent && this._parent._needUpdate > 0)) + { + _needUpdate = 1; + } + else + { + return; + } + + blendingTimeline(); + + //计算global + var result:Object = updateGlobal(); + var parentGlobalTransform:DBTransform = result ? result.parentGlobalTransform : null; + var parentGlobalTransformMatrix:Matrix = result ? result.parentGlobalTransformMatrix : null; + + //计算globalForChild + var ifExistOffsetTranslation:Boolean = _offset.x != 0 || _offset.y != 0; + var ifExistOffsetScale:Boolean = _offset.scaleX != 1 || _offset.scaleY != 1; + var ifExistOffsetRotation:Boolean = _offset.skewX != 0 || _offset.skewY != 0; + + if( (!ifExistOffsetTranslation || applyOffsetTranslationToChild) && + (!ifExistOffsetScale || applyOffsetScaleToChild) && + (!ifExistOffsetRotation || applyOffsetRotationToChild)) + { + _globalTransformForChild = _global; + _globalTransformMatrixForChild = _globalTransformMatrix; + } + else + { + if(!_tempGlobalTransformForChild) + { + _tempGlobalTransformForChild = new DBTransform(); + } + _globalTransformForChild = _tempGlobalTransformForChild; + + if(!_tempGlobalTransformMatrixForChild) + { + _tempGlobalTransformMatrixForChild = new Matrix(); + } + _globalTransformMatrixForChild = _tempGlobalTransformMatrixForChild; + + _globalTransformForChild.x = this._origin.x + _tween.x; + _globalTransformForChild.y = this._origin.y + _tween.y; + _globalTransformForChild.scaleX = this._origin.scaleX * _tween.scaleX; + _globalTransformForChild.scaleY = this._origin.scaleY * _tween.scaleY; + _globalTransformForChild.skewX = this._origin.skewX + _tween.skewX; + _globalTransformForChild.skewY = this._origin.skewY + _tween.skewY; + + if(applyOffsetTranslationToChild) + { + _globalTransformForChild.x += this._offset.x; + _globalTransformForChild.y += this._offset.y; + } + if(applyOffsetScaleToChild) + { + _globalTransformForChild.scaleX *= this._offset.scaleX; + _globalTransformForChild.scaleY *= this._offset.scaleY; + } + if(applyOffsetRotationToChild) + { + _globalTransformForChild.skewX += this._offset.skewX; + _globalTransformForChild.skewY += this._offset.skewY; + } + + TransformUtil.transformToMatrix(_globalTransformForChild, _globalTransformMatrixForChild); + if(parentGlobalTransformMatrix) + { + _globalTransformMatrixForChild.concat(parentGlobalTransformMatrix); + TransformUtil.matrixToTransform(_globalTransformMatrixForChild, _globalTransformForChild, _globalTransformForChild.scaleX * parentGlobalTransform.scaleX >= 0, _globalTransformForChild.scaleY * parentGlobalTransform.scaleY >= 0 ); + } + } + } + + /** @private */ + dragonBones_internal function hideSlots():void + { + for each(var childSlot:Slot in _slotList) + { + childSlot.changeDisplay(-1); + } + } + + /** @private When bone timeline enter a key frame, call this func*/ + dragonBones_internal function arriveAtFrame(frame:Frame, timelineState:TimelineState, animationState:AnimationState, isCross:Boolean):void + { + var displayControl:Boolean = + animationState.displayControl && + (!displayController || displayController == animationState.name) && + animationState.containsBoneMask(name) + + if(displayControl) + { + var childSlot:Slot; + if(frame.event && this._armature.hasEventListener(FrameEvent.BONE_FRAME_EVENT)) + { + var frameEvent:FrameEvent = new FrameEvent(FrameEvent.BONE_FRAME_EVENT); + frameEvent.bone = this; + frameEvent.animationState = animationState; + frameEvent.frameLabel = frame.event; + this._armature._eventList.push(frameEvent); + } + if(frame.sound && _soundManager.hasEventListener(SoundEvent.SOUND)) + { + var soundEvent:SoundEvent = new SoundEvent(SoundEvent.SOUND); + soundEvent.armature = this._armature; + soundEvent.animationState = animationState; + soundEvent.sound = frame.sound; + _soundManager.dispatchEvent(soundEvent); + } + + //[TODO]currently there is only gotoAndPlay belongs to frame action. In future, there will be more. + //后续会扩展更多的action,目前只有gotoAndPlay的含义 + if(frame.action) + { + for each(childSlot in _slotList) + { + var childArmature:Armature = childSlot.childArmature; + if(childArmature) + { + childArmature.animation.gotoAndPlay(frame.action); + } + } + } + } + } + + /** @private */ + dragonBones_internal function addState(timelineState:TimelineState):void + { + if(_timelineStateList.indexOf(timelineState) < 0) + { + _timelineStateList.push(timelineState); + _timelineStateList.sort(sortState); + } + } + + /** @private */ + dragonBones_internal function removeState(timelineState:TimelineState):void + { + var index:int = _timelineStateList.indexOf(timelineState); + if(index >= 0) + { + _timelineStateList.splice(index, 1); + } + } + + /** @private */ + dragonBones_internal function removeAllStates():void + { + _timelineStateList.length = 0; + } + + private function blendingTimeline():void + { + var timelineState:TimelineState; + var transform:DBTransform; + var pivot:Point; + var weight:Number; + + var i:int = _timelineStateList.length; + if(i == 1) + { + timelineState = _timelineStateList[0]; + weight = timelineState._animationState.weight * timelineState._animationState.fadeWeight; + timelineState._weight = weight; + transform = timelineState._transform; + pivot = timelineState._pivot; + + _tween.x = transform.x * weight; + _tween.y = transform.y * weight; + _tween.skewX = transform.skewX * weight; + _tween.skewY = transform.skewY * weight; + _tween.scaleX = 1 + (transform.scaleX - 1) * weight; + _tween.scaleY = 1 + (transform.scaleY - 1) * weight; + + _tweenPivot.x = pivot.x * weight; + _tweenPivot.y = pivot.y * weight; + } + else if(i > 1) + { + var x:Number = 0; + var y:Number = 0; + var skewX:Number = 0; + var skewY:Number = 0; + var scaleX:Number = 1; + var scaleY:Number = 1; + var pivotX:Number = 0; + var pivotY:Number = 0; + + var weigthLeft:Number = 1; + var layerTotalWeight:Number = 0; + var prevLayer:int = _timelineStateList[i - 1]._animationState.layer; + var currentLayer:int; + + //Traversal the layer from up to down + //layer由高到低依次遍历 + + while(i --) + { + timelineState = _timelineStateList[i]; + + currentLayer = timelineState._animationState.layer; + if(prevLayer != currentLayer) + { + if(layerTotalWeight >= weigthLeft) + { + timelineState._weight = 0; + break; + } + else + { + weigthLeft -= layerTotalWeight; + } + } + prevLayer = currentLayer; + + weight = timelineState._animationState.weight * timelineState._animationState.fadeWeight * weigthLeft; + timelineState._weight = weight; + if(weight && timelineState._blendEnabled) + { + transform = timelineState._transform; + pivot = timelineState._pivot; + + x += transform.x * weight; + y += transform.y * weight; + skewX += transform.skewX * weight; + skewY += transform.skewY * weight; + scaleX += (transform.scaleX - 1) * weight; + scaleY += (transform.scaleY - 1) * weight; + pivotX += pivot.x * weight; + pivotY += pivot.y * weight; + + layerTotalWeight += weight; + } + } + + _tween.x = x; + _tween.y = y; + _tween.skewX = skewX; + _tween.skewY = skewY; + _tween.scaleX = scaleX; + _tween.scaleY = scaleY; + _tweenPivot.x = pivotX; + _tweenPivot.y = pivotY; + } + } + + private function sortState(state1:TimelineState, state2:TimelineState):int + { + return state1._animationState.layer < state2._animationState.layer?-1:1; + } + + /** + * Unrecommended API. Recommend use slot.childArmature. + */ + public function get childArmature():Armature + { + if(slot) + { + return slot.childArmature; + } + return null; + } + + /** + * Unrecommended API. Recommend use slot.display. + */ + public function get display():Object + { + if(slot) + { + return slot.display; + } + return null; + } + public function set display(value:Object):void + { + if(slot) + { + slot.display = value; + } + } + + /** + * Unrecommended API. Recommend use offset. + */ + public function get node():DBTransform + { + return _offset; + } + + + + + /** @private */ + override public function set visible(value:Boolean):void + { + if(this._visible != value) + { + this._visible = value; + for each(var childSlot:Slot in _slotList) + { + childSlot.updateDisplayVisible(this._visible); + } + } + } + + + public function get slot():Slot + { + return _slotList.length > 0?_slotList[0]:null; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/Slot.as b/srclib/dragonBones/Slot.as new file mode 100644 index 00000000..1eb05f54 --- /dev/null +++ b/srclib/dragonBones/Slot.as @@ -0,0 +1,578 @@ +package dragonBones { + + import dragonBones.animation.AnimationState; + import dragonBones.animation.SlotTimelineState; + import dragonBones.core.DBObject; + import dragonBones.core.dragonBones_internal; + import dragonBones.objects.DisplayData; + import dragonBones.objects.Frame; + import dragonBones.objects.SlotData; + import dragonBones.objects.SlotFrame; + import dragonBones.utils.TransformUtil; + + import flash.errors.IllegalOperationError; + import flash.geom.ColorTransform; + import flash.geom.Matrix; + + //import dragonBones.objects.FrameCached; + //import dragonBones.objects.TimelineCached; + + use namespace dragonBones_internal; + + public class Slot extends DBObject + { + /** @private Need to keep the reference of DisplayData. When slot switch displayObject, it need to restore the display obect's origional pivot. */ + dragonBones_internal var _displayDataList:Vector.; + /** @private */ + dragonBones_internal var _originZOrder:Number; + /** @private */ + dragonBones_internal var _tweenZOrder:Number; + /** @private */ + protected var _offsetZOrder:Number; + + protected var _displayList:Array; + protected var _currentDisplayIndex:int; + protected var _colorTransform:ColorTransform; + //TO DO: 以后把这两个属性变成getter + //另外还要处理 isShowDisplay 和 visible的矛盾 + protected var _currentDisplay:Object; + dragonBones_internal var _isShowDisplay:Boolean; + + //protected var _childArmature:Armature; + protected var _blendMode:String; + + /** @private */ + dragonBones_internal var _isColorChanged:Boolean; + /** @private */ +// protected var _timelineStateList:Vector.; + + public function Slot(self:Slot) + { + super(); + + if(self != this) + { + throw new IllegalOperationError("Abstract class can not be instantiated!"); + } + + _displayList = []; + _currentDisplayIndex = -1; + + _originZOrder = 0; + _tweenZOrder = 0; + _offsetZOrder = 0; + _isShowDisplay = false; + _isColorChanged = false; + _colorTransform = new ColorTransform(); + _displayDataList = null; + //_childArmature = null; + _currentDisplay = null; +// _timelineStateList = new Vector.; + + this.inheritRotation = true; + this.inheritScale = true; + } + + public function initWithSlotData(slotData:SlotData):void + { + name = slotData.name; + blendMode = slotData.blendMode; + _originZOrder = slotData.zOrder; + _displayDataList = slotData.displayDataList; + } + + /** + * @inheritDoc + */ + override public function dispose():void + { + if(!_displayList) + { + return; + } + + super.dispose(); + + _displayList.length = 0; +// _timelineStateList.length = 0; + + _displayDataList = null; + _displayList = null; + _currentDisplay = null; +// _timelineStateList = null; + + } + +// private function sortState(state1:SlotTimelineState, state2:SlotTimelineState):int +// { +// return state1._animationState.layer < state2._animationState.layer?-1:1; +// } + + /** @private */ +// dragonBones_internal function addState(timelineState:SlotTimelineState):void +// { +// if(_timelineStateList.indexOf(timelineState) < 0) +// { +// _timelineStateList.push(timelineState); +// _timelineStateList.sort(sortState); +// } +// } + + /** @private */ +// dragonBones_internal function removeState(timelineState:SlotTimelineState):void +// { +// var index:int = _timelineStateList.indexOf(timelineState); +// if(index >= 0) +// { +// _timelineStateList.splice(index, 1); +// } +// } + +//骨架装配 + /** @private */ + override dragonBones_internal function setArmature(value:Armature):void + { + if(_armature == value) + { + return; + } + if(_armature) + { + _armature.removeSlotFromSlotList(this); + } + _armature = value; + if(_armature) + { + _armature.addSlotToSlotList(this); + _armature._slotsZOrderChanged = true; + addDisplayToContainer(this._armature.display); + } + else + { + removeDisplayFromContainer(); + } + } + +//动画 + /** @private */ + dragonBones_internal function update():void + { + if(this._parent._needUpdate <= 0) + { + return; + } + + updateGlobal(); + updateTransform(); + } + + override protected function calculateRelativeParentTransform():void + { + _global.scaleX = this._origin.scaleX * this._offset.scaleX; + _global.scaleY = this._origin.scaleY * this._offset.scaleY; + _global.skewX = this._origin.skewX + this._offset.skewX; + _global.skewY = this._origin.skewY + this._offset.skewY; + _global.x = this._origin.x + this._offset.x + this._parent._tweenPivot.x; + _global.y = this._origin.y + this._offset.y + this._parent._tweenPivot.y; + } + + private function updateChildArmatureAnimation():void + { + if(childArmature) + { + if(_isShowDisplay) + { + if( + this._armature && + this._armature.animation.lastAnimationState && + childArmature.animation.hasAnimation(this._armature.animation.lastAnimationState.name) + ) + { + childArmature.animation.gotoAndPlay(this._armature.animation.lastAnimationState.name); + } + else + { + childArmature.animation.play(); + } + } + else + { + childArmature.animation.stop(); + childArmature.animation._lastAnimationState = null; + } + } + } + + /** @private */ + dragonBones_internal function changeDisplay(displayIndex:int):void + { + if (displayIndex < 0) + { + if(_isShowDisplay) + { + _isShowDisplay = false; + removeDisplayFromContainer(); + updateChildArmatureAnimation(); + } + } + else if (_displayList.length > 0) + { + var length:uint = _displayList.length; + if(displayIndex >= length) + { + displayIndex = length - 1; + } + + if(_currentDisplayIndex != displayIndex) + { + _isShowDisplay = true; + _currentDisplayIndex = displayIndex; + updateSlotDisplay(); + //updateTransform();//解决当时间和bone不统一时会换皮肤时会跳的bug + updateChildArmatureAnimation(); + if( + _displayDataList && + _displayDataList.length > 0 && + _currentDisplayIndex < _displayDataList.length + ) + { + this._origin.copy(_displayDataList[_currentDisplayIndex].transform); + } + } + else if(!_isShowDisplay) + { + _isShowDisplay = true; + if(this._armature) + { + this._armature._slotsZOrderChanged = true; + addDisplayToContainer(this._armature.display); + } + updateChildArmatureAnimation(); + } + + } + } + + /** @private + * Updates the display of the slot. + */ + dragonBones_internal function updateSlotDisplay():void + { + var currentDisplayIndex:int = -1; + if(_currentDisplay) + { + currentDisplayIndex = getDisplayIndex(); + removeDisplayFromContainer(); + } + var displayObj:Object = _displayList[_currentDisplayIndex]; + if (displayObj) + { + if(displayObj is Armature) + { + //_childArmature = display as Armature; + _currentDisplay = (displayObj as Armature).display; + } + else + { + //_childArmature = null; + _currentDisplay = displayObj; + } + } + else + { + _currentDisplay = null; + //_childArmature = null; + } + updateDisplay(_currentDisplay); + if(_currentDisplay) + { + if(this._armature && _isShowDisplay) + { + if(currentDisplayIndex < 0) + { + this._armature._slotsZOrderChanged = true; + addDisplayToContainer(this._armature.display); + } + else + { + addDisplayToContainer(this._armature.display, currentDisplayIndex); + } + } + updateDisplayBlendMode(_blendMode); + updateDisplayColor( _colorTransform.alphaOffset, _colorTransform.redOffset, _colorTransform.greenOffset, _colorTransform.blueOffset, + _colorTransform.alphaMultiplier, _colorTransform.redMultiplier, _colorTransform.greenMultiplier, _colorTransform.blueMultiplier); + updateDisplayVisible(_visible); + updateTransform(); + } + } + + /** @private */ + override public function set visible(value:Boolean):void + { + if(this._visible != value) + { + this._visible = value; + updateDisplayVisible(this._visible); + } + } + + /** + * The DisplayObject list belonging to this Slot instance (display or armature). Replace it to implement switch texture. + */ + public function get displayList():Array + { + return _displayList; + } + public function set displayList(value:Array):void + { + if(!value) + { + throw new ArgumentError(); + } + + //为什么要修改_currentDisplayIndex? + if (_currentDisplayIndex < 0) + { + _currentDisplayIndex = 0; + } + var i:int = _displayList.length = value.length; + while(i --) + { + _displayList[i] = value[i]; + } + + //在index不改变的情况下强制刷新 TO DO需要修改 + var displayIndexBackup:int = _currentDisplayIndex; + _currentDisplayIndex = -1; + changeDisplay(displayIndexBackup); + updateTransform(); + } + + /** + * The DisplayObject belonging to this Slot instance. Instance type of this object varies from flash.display.DisplayObject to startling.display.DisplayObject and subclasses. + */ + public function get display():Object + { + return _currentDisplay; + } + public function set display(value:Object):void + { + if (_currentDisplayIndex < 0) + { + _currentDisplayIndex = 0; + } + if(_displayList[_currentDisplayIndex] == value) + { + return; + } + _displayList[_currentDisplayIndex] = value; + updateSlotDisplay(); + updateChildArmatureAnimation(); + updateTransform();//是否可以延迟更新? + } + + /** + * The sub-armature of this Slot instance. + */ + public function get childArmature():Armature + { + return _displayList[_currentDisplayIndex] is Armature ? _displayList[_currentDisplayIndex] : null; + } + public function set childArmature(value:Armature):void + { + //设计的不好,要修改 + display = value; + } + + /** + * zOrder. Support decimal for ensure dynamically added slot work toghther with animation controled slot. + * @return zOrder. + */ + public function get zOrder():Number + { + return _originZOrder + _tweenZOrder + _offsetZOrder; + } + public function set zOrder(value:Number):void + { + if(zOrder != value) + { + _offsetZOrder = value - _originZOrder - _tweenZOrder; + if(this._armature) + { + this._armature._slotsZOrderChanged = true; + } + } + } + + /** + * blendMode + * @return blendMode. + */ + public function get blendMode():String + { + return _blendMode; + } + public function set blendMode(value:String):void + { + if(_blendMode != value) + { + _blendMode = value; + updateDisplayBlendMode(_blendMode); + } + } + + //Abstract method + + /** + * @private + */ + dragonBones_internal function updateDisplay(value:Object):void + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + */ + dragonBones_internal function getDisplayIndex():int + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + * Adds the original display object to another display object. + * @param container + * @param index + */ + dragonBones_internal function addDisplayToContainer(container:Object, index:int = -1):void + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + * remove the original display object from its parent. + */ + dragonBones_internal function removeDisplayFromContainer():void + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + * Updates the transform of the slot. + */ + dragonBones_internal function updateTransform():void + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + */ + dragonBones_internal function updateDisplayVisible(value:Boolean):void + { + /** + * bone.visible && slot.visible && updateVisible + * this._parent.visible && this._visible && value; + */ + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + * Updates the color of the display object. + * @param a + * @param r + * @param g + * @param b + * @param aM + * @param rM + * @param gM + * @param bM + */ + dragonBones_internal function updateDisplayColor( + aOffset:Number, + rOffset:Number, + gOffset:Number, + bOffset:Number, + aMultiplier:Number, + rMultiplier:Number, + gMultiplier:Number, + bMultiplier:Number, + colorChanged:Boolean = false + ):void + { + _colorTransform.alphaOffset = aOffset; + _colorTransform.redOffset = rOffset; + _colorTransform.greenOffset = gOffset; + _colorTransform.blueOffset = bOffset; + _colorTransform.alphaMultiplier = aMultiplier; + _colorTransform.redMultiplier = rMultiplier; + _colorTransform.greenMultiplier = gMultiplier; + _colorTransform.blueMultiplier = bMultiplier; + + _isColorChanged = colorChanged; + } + + /** + * @private + * Update the blend mode of the display object. + * @param value The blend mode to use. + */ + dragonBones_internal function updateDisplayBlendMode(value:String):void + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** @private When slot timeline enter a key frame, call this func*/ + dragonBones_internal function arriveAtFrame(frame:Frame, timelineState:SlotTimelineState, animationState:AnimationState, isCross:Boolean):void + { + var displayControl:Boolean = animationState.displayControl && + animationState.containsBoneMask(parent.name) + + if(displayControl) + { + var slotFrame:SlotFrame = frame as SlotFrame; + var displayIndex:int = slotFrame.displayIndex; + var childSlot:Slot; + changeDisplay(displayIndex); + updateDisplayVisible(slotFrame.visible); + if(displayIndex >= 0) + { + if(!isNaN(slotFrame.zOrder) && slotFrame.zOrder != _tweenZOrder) + { + _tweenZOrder = slotFrame.zOrder; + this._armature._slotsZOrderChanged = true; + } + } + + //[TODO]currently there is only gotoAndPlay belongs to frame action. In future, there will be more. + //后续会扩展更多的action,目前只有gotoAndPlay的含义 + if(frame.action) + { + if (childArmature) + { + childArmature.animation.gotoAndPlay(frame.action); + } + } + } + } + + override protected function updateGlobal():Object + { + calculateRelativeParentTransform(); + TransformUtil.transformToMatrix(_global, _globalTransformMatrix); + var output:Object = calculateParentTransform(); + if(output != null) + { + //计算父骨头绝对坐标 + var parentMatrix:Matrix = output.parentGlobalTransformMatrix; + _globalTransformMatrix.concat(parentMatrix); + } + TransformUtil.matrixToTransform(_globalTransformMatrix,_global,true,true); + return output; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/animation/Animation.as b/srclib/dragonBones/animation/Animation.as new file mode 100644 index 00000000..033abe5b --- /dev/null +++ b/srclib/dragonBones/animation/Animation.as @@ -0,0 +1,535 @@ +package dragonBones.animation { + + import dragonBones.Armature; + import dragonBones.Slot; + import dragonBones.core.dragonBones_internal; + import dragonBones.objects.AnimationData; + + use namespace dragonBones_internal; + + /** + * An Animation instance is used to control the animation state of an Armature. + * @see dragonBones.Armature + * @see dragonBones.animation.Animation + * @see dragonBones.animation.AnimationState + */ + public class Animation + { + public static const NONE:String = "none"; + public static const SAME_LAYER:String = "sameLayer"; + public static const SAME_GROUP:String = "sameGroup"; + public static const SAME_LAYER_AND_GROUP:String = "sameLayerAndGroup"; + public static const ALL:String = "all"; + + /** + * Whether animation tweening is enabled or not. + */ + public var tweenEnabled:Boolean; + private var _armature:Armature; + private var _animationStateList:Vector.; + private var _animationDataList:Vector.; + private var _animationList:Vector.; + private var _isPlaying:Boolean; + private var _timeScale:Number; + + /** @private */ + dragonBones_internal var _lastAnimationState:AnimationState; + + /** @private */ + dragonBones_internal var _isFading:Boolean + + /** @private */ + dragonBones_internal var _animationStateCount:int; + + + /** + * Creates a new Animation instance and attaches it to the passed Armature. + * @param armature An Armature to attach this Animation instance to. + */ + public function Animation(armature:Armature) + { + _armature = armature; + _animationList = new Vector.; + _animationStateList = new Vector.; + + _timeScale = 1; + _isPlaying = false; + + tweenEnabled = true; + } + + /** + * Qualifies all resources used by this Animation instance for garbage collection. + */ + public function dispose():void + { + if(!_armature) + { + return; + } + + resetAnimationStateList(); + + _animationList.length = 0; + + _armature = null; + _animationDataList = null; + _animationList = null; + _animationStateList = null; + } + + dragonBones_internal function resetAnimationStateList():void + { + var i:int = _animationStateList.length; + var animationState:AnimationState; + while(i --) + { + animationState = _animationStateList[i]; + animationState.resetTimelineStateList(); + AnimationState.returnObject(animationState); + } + _animationStateList.length = 0; + } + + /** + * Fades the animation with name animation in over a period of time seconds and fades other animations out. + * @param animationName The name of the AnimationData to play. + * @param fadeInTime A fade time to apply (>= 0), -1 means use xml data's fadeInTime. + * @param duration The duration of that Animation. -1 means use xml data's duration. + * @param playTimes Play times(0:loop forever, >=1:play times, -1~-∞:will fade animation after play complete), 默认使用AnimationData.loop. + * @param layer The layer of the animation. + * @param group The group of the animation. + * @param fadeOutMode Fade out mode (none, sameLayer, sameGroup, sameLayerAndGroup, all). + * @param pauseFadeOut Pause other animation playing. + * @param pauseFadeIn Pause this animation playing before fade in complete. + * @return AnimationState. + * @see dragonBones.objects.AnimationData. + * @see dragonBones.animation.AnimationState. + */ + public function gotoAndPlay( + animationName:String, + fadeInTime:Number = -1, + duration:Number = -1, + playTimes:Number = Number.NaN, + layer:int = 0, + group:String = null, + fadeOutMode:String = SAME_LAYER_AND_GROUP, + pauseFadeOut:Boolean = true, + pauseFadeIn:Boolean = true + ):AnimationState + { + if (!_animationDataList) + { + return null; + } + var i:int = _animationDataList.length; + var animationData:AnimationData; + while(i --) + { + if(_animationDataList[i].name == animationName) + { + animationData = _animationDataList[i]; + break; + } + } + if (!animationData) + { + return null; + } + _isPlaying = true; + _isFading = true; + + // + fadeInTime = fadeInTime < 0?(animationData.fadeTime < 0?0.3:animationData.fadeTime):fadeInTime; + var durationScale:Number; + if(duration < 0) + { + durationScale = animationData.scale < 0?1:animationData.scale; + } + else + { + durationScale = duration * 1000 / animationData.duration; + } + + playTimes = isNaN(playTimes)?animationData.playTimes:playTimes; + + //根据fadeOutMode,选择正确的animationState执行fadeOut + var animationState:AnimationState; + switch(fadeOutMode) + { + case NONE: + break; + + case SAME_LAYER: + i = _animationStateList.length; + while(i --) + { + animationState = _animationStateList[i]; + if(animationState.layer == layer) + { + animationState.fadeOut(fadeInTime, pauseFadeOut); + } + } + break; + + case SAME_GROUP: + i = _animationStateList.length; + while(i --) + { + animationState = _animationStateList[i]; + if(animationState.group == group) + { + animationState.fadeOut(fadeInTime, pauseFadeOut); + } + } + break; + + case ALL: + i = _animationStateList.length; + while(i --) + { + animationState = _animationStateList[i]; + animationState.fadeOut(fadeInTime, pauseFadeOut); + } + break; + + case SAME_LAYER_AND_GROUP: + default: + i = _animationStateList.length; + while(i --) + { + animationState = _animationStateList[i]; + if(animationState.layer == layer && animationState.group == group ) + { + animationState.fadeOut(fadeInTime, pauseFadeOut); + } + } + break; + } + + _lastAnimationState = AnimationState.borrowObject(); + _lastAnimationState._layer = layer; + _lastAnimationState._group = group; + _lastAnimationState.autoTween = tweenEnabled; + _lastAnimationState.fadeIn(_armature, animationData, fadeInTime, 1 / durationScale, playTimes, pauseFadeIn); + + addState(_lastAnimationState); + + //控制子骨架播放同名动画 + var slotList:Vector. = _armature.getSlots(false); + i = slotList.length; + while(i --) + { + var slot:Slot = slotList[i]; + if(slot.childArmature) + { + slot.childArmature.animation.gotoAndPlay(animationName, fadeInTime); + } + } + + return _lastAnimationState; + } + + /** + * Control the animation to stop with a specified time. If related animationState haven't been created, then create a new animationState. + * @param animationName The name of the animationState. + * @param time + * @param normalizedTime + * @param fadeInTime A fade time to apply (>= 0), -1 means use xml data's fadeInTime. + * @param duration The duration of that Animation. -1 means use xml data's duration. + * @param layer The layer of the animation. + * @param group The group of the animation. + * @param fadeOutMode Fade out mode (none, sameLayer, sameGroup, sameLayerAndGroup, all). + * @return AnimationState. + * @see dragonBones.objects.AnimationData. + * @see dragonBones.animation.AnimationState. + */ + public function gotoAndStop( + animationName:String, + time:Number, + normalizedTime:Number = -1, + fadeInTime:Number = 0, + duration:Number = -1, + layer:int = 0, + group:String = null, + fadeOutMode:String = ALL + ):AnimationState + { + var animationState:AnimationState = getState(animationName, layer); + if(!animationState) + { + animationState = gotoAndPlay(animationName, fadeInTime, duration, NaN, layer, group, fadeOutMode); + } + + if(normalizedTime >= 0) + { + animationState.setCurrentTime(animationState.totalTime * normalizedTime); + } + else + { + animationState.setCurrentTime(time); + } + + animationState.stop(); + + return animationState; + } + + /** + * Play the animation from the current position. + */ + public function play():void + { + if (!_animationDataList || _animationDataList.length == 0) + { + return; + } + if(!_lastAnimationState) + { + gotoAndPlay(_animationDataList[0].name); + } + else if (!_isPlaying) + { + _isPlaying = true; + } + else + { + gotoAndPlay(_lastAnimationState.name); + } + } + + public function stop():void + { + _isPlaying = false; + } + + /** + * Returns the AnimationState named name. + * @return A AnimationState instance. + * @see dragonBones.animation.AnimationState. + */ + public function getState(name:String, layer:int = 0):AnimationState + { + var i:int = _animationStateList.length; + while(i --) + { + var animationState:AnimationState = _animationStateList[i]; + if(animationState.name == name && animationState.layer == layer) + { + return animationState; + } + } + return null; + } + + /** + * check if contains a AnimationData by name. + * @return Boolean. + * @see dragonBones.animation.AnimationData. + */ + public function hasAnimation(animationName:String):Boolean + { + var i:int = _animationDataList.length; + while(i --) + { + if(_animationDataList[i].name == animationName) + { + return true; + } + } + + return false; + } + + /** @private */ + dragonBones_internal function advanceTime(passedTime:Number):void + { + if(!_isPlaying) + { + return; + } + + var isFading:Boolean = false; + + passedTime *= _timeScale; + var i:int = _animationStateList.length; + while(i --) + { + var animationState:AnimationState = _animationStateList[i]; + if(animationState.advanceTime(passedTime)) + { + removeState(animationState); + } + else if(animationState.fadeState != 1) + { + isFading = true; + } + } + + _isFading = isFading; + } + + /** @private */ + //当动画播放过程中Bonelist改变时触发 + dragonBones_internal function updateAnimationStates():void + { + var i:int = _animationStateList.length; + while(i --) + { + _animationStateList[i].updateTimelineStates(); + } + } + + private function addState(animationState:AnimationState):void + { + if(_animationStateList.indexOf(animationState) < 0) + { + _animationStateList.unshift(animationState); + + _animationStateCount = _animationStateList.length; + } + } + + private function removeState(animationState:AnimationState):void + { + var index:int = _animationStateList.indexOf(animationState); + if(index >= 0) + { + _animationStateList.splice(index, 1); + AnimationState.returnObject(animationState); + + if(_lastAnimationState == animationState) + { + if(_animationStateList.length > 0) + { + _lastAnimationState = _animationStateList[0]; + } + else + { + _lastAnimationState = null; + } + } + + _animationStateCount = _animationStateList.length; + } + } + + + + /** + * Unrecommended API. Recommend use animationList. + */ + public function get movementList():Vector. + { + return _animationList; + } + + /** + * Unrecommended API. Recommend use lastAnimationName. + */ + public function get movementID():String + { + return lastAnimationName; + } + + + + /** + * The last AnimationState this Animation played. + * @see dragonBones.objects.AnimationData. + */ + public function get lastAnimationState():AnimationState + { + return _lastAnimationState; + } + /** + * The name of the last AnimationData played. + * @see dragonBones.objects.AnimationData. + */ + public function get lastAnimationName():String + { + return _lastAnimationState?_lastAnimationState.name:null; + } + + + /** + * An vector containing all AnimationData names the Animation can play. + * @see dragonBones.objects.AnimationData. + */ + public function get animationList():Vector. + { + return _animationList; + } + + + /** + * Is the animation playing. + * @see dragonBones.animation.AnimationState. + */ + public function get isPlaying():Boolean + { + return _isPlaying && !isComplete; + } + + /** + * Is animation complete. + * @see dragonBones.animation.AnimationState. + */ + public function get isComplete():Boolean + { + if(_lastAnimationState) + { + if(!_lastAnimationState.isComplete) + { + return false; + } + var i:int = _animationStateList.length; + while(i --) + { + if(!_animationStateList[i].isComplete) + { + return false; + } + } + return true; + } + return true; + } + + + /** + * The amount by which passed time should be scaled. Used to slow down or speed up animations. Defaults to 1. + */ + public function get timeScale():Number + { + return _timeScale; + } + public function set timeScale(value:Number):void + { + if(isNaN(value) || value < 0) + { + value = 1; + } + _timeScale = value; + } + + /** + * The AnimationData list associated with this Animation instance. + * @see dragonBones.objects.AnimationData. + */ + public function get animationDataList():Vector. + { + return _animationDataList; + } + public function set animationDataList(value:Vector.):void + { + _animationDataList = value; + _animationList.length = 0; + for each(var animationData:AnimationData in _animationDataList) + { + _animationList[_animationList.length] = animationData.name; + } + } + + } +} diff --git a/srclib/dragonBones/animation/AnimationState.as b/srclib/dragonBones/animation/AnimationState.as new file mode 100644 index 00000000..5784a4c9 --- /dev/null +++ b/srclib/dragonBones/animation/AnimationState.as @@ -0,0 +1,995 @@ +package dragonBones.animation { + + import dragonBones.Armature; + import dragonBones.Bone; + import dragonBones.Slot; + import dragonBones.core.dragonBones_internal; + import dragonBones.events.AnimationEvent; + import dragonBones.objects.AnimationData; + import dragonBones.objects.Frame; + import dragonBones.objects.SlotTimeline; + import dragonBones.objects.TransformTimeline; + + use namespace dragonBones_internal; + /** + * The AnimationState gives full control over animation blending. + * In most cases the Animation interface is sufficient and easier to use. Use the AnimationState if you need full control over the animation blending any playback process. + */ + final public class AnimationState + { + private static var _pool:Vector. = new Vector.; + + /** @private */ + dragonBones_internal static function borrowObject():AnimationState + { + if(_pool.length == 0) + { + return new AnimationState(); + } + return _pool.pop(); + } + + /** @private */ + dragonBones_internal static function returnObject(animationState:AnimationState):void + { + animationState.clear(); + + if(_pool.indexOf(animationState) < 0) + { + _pool[_pool.length] = animationState; + } + } + + /** @private */ + dragonBones_internal static function clear():void + { + var i:int = _pool.length; + while(i --) + { + _pool[i].clear(); + } + _pool.length = 0; + + TimelineState.clear(); + } + + /** + * Sometimes, we want slots controlled by a spedific animation state when animation is doing mix or addition. + * It determine if animation's color change, displayIndex change, visible change can apply to its display + */ + public var displayControl:Boolean; + + /** + * If animation mixing use additive blending. + */ + public var additiveBlending:Boolean; + + /** + * If animation auto fade out after play complete. + */ + public var autoFadeOut:Boolean; + /** + * Duration of fade out. By default, it equals to fade in time. + */ + public var fadeOutTime:Number; + + /** + * The weight of animation. + */ + public var weight:Number; + + /** + * If auto genterate tween between keyframes. + */ + public var autoTween:Boolean; + /** + * If generate tween between the lastFrame to the first frame for loop animation. + */ + public var lastFrameAutoTween:Boolean; + + /** @private */ + dragonBones_internal var _layer:int; + /** @private */ + dragonBones_internal var _group:String; + + private var _armature:Armature; + private var _timelineStateList:Vector.; + private var _slotTimelineStateList:Vector.; + private var _boneMasks:Vector.; + + private var _isPlaying:Boolean; + private var _time:Number; + private var _currentFrameIndex:int; + private var _currentFramePosition:int; + private var _currentFrameDuration:int; + + //Fadein 的时候是否先暂停 + private var _pausePlayheadInFade:Boolean; + private var _isFadeOut:Boolean; + //最终的真实权重值 + private var _fadeTotalWeight:Number; + //受fade影响的动作权重系数,在fadein阶段他的值会由0变为1,在fadeout阶段会由1变为0 + private var _fadeWeight:Number; + private var _fadeCurrentTime:Number; + private var _fadeBeginTime:Number; + + private var _name:String; + private var _clip:AnimationData; + private var _isComplete:Boolean; + private var _currentPlayTimes:int; + private var _totalTime:int; + private var _currentTime:int; + private var _lastTime:int; + //-1 beforeFade, 0 fading, 1 fadeComplete + private var _fadeState:int; + private var _fadeTotalTime:Number; + + //时间缩放参数, 各帧duration数据不变的情况下,让传入时间*timeScale 实现durationScale + private var _timeScale:Number; + private var _playTimes:int; + + public function AnimationState() + { + _timelineStateList = new Vector.; + _slotTimelineStateList = new Vector.; + _boneMasks = new Vector.; + } + + private function clear():void + { + resetTimelineStateList(); + + _boneMasks.length = 0; + + _armature = null; + _clip = null; + } + + dragonBones_internal function resetTimelineStateList():void + { + var i:int = _timelineStateList.length; + while(i --) + { + TimelineState.returnObject(_timelineStateList[i]); + } + _timelineStateList.length = 0; + + i = _slotTimelineStateList.length; + while(i --) + { + SlotTimelineState.returnObject(_slotTimelineStateList[i]); + } + _slotTimelineStateList.length = 0; + } + +//骨架装配 + public function containsBoneMask(boneName:String):Boolean + { + return _boneMasks.length == 0 || _boneMasks.indexOf(boneName) >= 0; + } + + /** + * Adds a bone which should be animated. This allows you to reduce the number of animations you have to create. + * @param boneName Bone's name. + * @param ifInvolveChildBones if involve child bone's animation. + */ + public function addBoneMask(boneName:String, ifInvolveChildBones:Boolean = true):AnimationState + { + addBoneToBoneMask(boneName); + + if(ifInvolveChildBones) + { + var currentBone:Bone = _armature.getBone(boneName); + if(currentBone) + { + var boneList:Vector. = _armature.getBones(false); + var i:int = boneList.length; + while(i--) + { + var tempBone:Bone = boneList[i]; + if(currentBone.contains(tempBone)) + { + addBoneToBoneMask(tempBone.name); + } + } + } + } + + updateTimelineStates(); + return this; + } + + /** + * Removes a bone which was supposed be animated. + * @param boneName Bone's timeline name. + * @param ifInvolveChildBones If involved child bone's timeline. + */ + public function removeBoneMask(boneName:String, ifInvolveChildBones:Boolean = true):AnimationState + { + removeBoneFromBoneMask(boneName); + + if(ifInvolveChildBones) + { + var currentBone:Bone = _armature.getBone(boneName); + if(currentBone) + { + var boneList:Vector. = _armature.getBones(false); + var i:int = boneList.length; + while(i--) + { + var tempBone:Bone = boneList[i]; + if(currentBone.contains(tempBone)) + { + removeBoneFromBoneMask(tempBone.name); + } + } + } + } + updateTimelineStates(); + + return this; + } + + public function removeAllMixingTransform():AnimationState + { + _boneMasks.length = 0; + updateTimelineStates(); + return this; + } + + private function addBoneToBoneMask(boneName:String):void + { + if(_clip.getTimeline(boneName) && _boneMasks.indexOf(boneName)<0) + { + _boneMasks.push(boneName); + } + } + + private function removeBoneFromBoneMask(boneName:String):void + { + var index:int = _boneMasks.indexOf(boneName); + if(index >= 0) + { + _boneMasks.splice(index, 1); + } + } + + /** + * @private + * Update timeline state based on mixing transforms and clip. + */ + dragonBones_internal function updateTimelineStates():void + { + var timelineState:TimelineState; + var slotTimelineState:SlotTimelineState; + var i:int = _timelineStateList.length; + while(i --) + { + timelineState = _timelineStateList[i]; + if(!_armature.getBone(timelineState.name)) + { + removeTimelineState(timelineState); + } + } + + i = _slotTimelineStateList.length; + while (i --) + { + slotTimelineState = _slotTimelineStateList[i]; + if (!_armature.getSlot(slotTimelineState.name)) + { + removeSlotTimelineState(slotTimelineState); + } + } + + if(_boneMasks.length > 0) + { + i = _timelineStateList.length; + while(i --) + { + timelineState = _timelineStateList[i]; + if(_boneMasks.indexOf(timelineState.name) < 0) + { + removeTimelineState(timelineState); + } + } + + for each(var timelineName:String in _boneMasks) + { + addTimelineState(timelineName); + } + } + else + { + for each(var timeline:TransformTimeline in _clip.timelineList) + { + addTimelineState(timeline.name); + } + } + + for each(var slotTimeline:SlotTimeline in _clip.slotTimelineList) + { + addSlotTimelineState(slotTimeline.name); + } + } + + private function addTimelineState(timelineName:String):void + { + var bone:Bone = _armature.getBone(timelineName); + if(bone) + { + for each(var eachState:TimelineState in _timelineStateList) + { + if(eachState.name == timelineName) + { + return; + } + } + var timelineState:TimelineState = TimelineState.borrowObject(); + timelineState.fadeIn(bone, this, _clip.getTimeline(timelineName)); + _timelineStateList.push(timelineState); + } + } + + private function removeTimelineState(timelineState:TimelineState):void + { + var index:int = _timelineStateList.indexOf(timelineState); + _timelineStateList.splice(index, 1); + TimelineState.returnObject(timelineState); + } + + private function addSlotTimelineState(timelineName:String):void + { + var slot:Slot = _armature.getSlot(timelineName); + if(slot && slot.displayList.length > 0) + { + for each(var eachState:SlotTimelineState in _slotTimelineStateList) + { + if(eachState.name == timelineName) + { + return; + } + } + var timelineState:SlotTimelineState = SlotTimelineState.borrowObject(); + timelineState.fadeIn(slot, this, _clip.getSlotTimeline(timelineName)); + _slotTimelineStateList.push(timelineState); + } + } + + private function removeSlotTimelineState(timelineState:SlotTimelineState):void + { + var index:int = _slotTimelineStateList.indexOf(timelineState); + _slotTimelineStateList.splice(index, 1); + SlotTimelineState.returnObject(timelineState); + } + + //动画 + /** + * Play the current animation. 如果动画已经播放完毕, 将不会继续播放. + */ + public function play():AnimationState + { + _isPlaying = true; + return this; + } + + /** + * Stop playing current animation. + */ + public function stop():AnimationState + { + _isPlaying = false; + return this; + } + + /** @private */ + dragonBones_internal function fadeIn(armature:Armature, clip:AnimationData, fadeTotalTime:Number, timeScale:Number, playTimes:Number, pausePlayhead:Boolean):AnimationState + { + _armature = armature; + _clip = clip; + _pausePlayheadInFade = pausePlayhead; + + _name = _clip.name; + _totalTime = _clip.duration; + + autoTween = _clip.autoTween; + + setTimeScale(timeScale); + setPlayTimes(playTimes); + + //reset + _isComplete = false; + _currentFrameIndex = -1; + _currentPlayTimes = -1; + if(Math.round(_totalTime * _clip.frameRate * 0.001) < 2 || timeScale == Infinity) + { + _currentTime = _totalTime; + } + else + { + _currentTime = -1; + } + _time = 0; + _boneMasks.length = 0; + + //fade start + _isFadeOut = false; + _fadeWeight = 0; + _fadeTotalWeight = 1; + _fadeState = -1; + _fadeCurrentTime = 0; + _fadeBeginTime = _fadeCurrentTime; + _fadeTotalTime = fadeTotalTime * _timeScale; + + //default + _isPlaying = true; + displayControl = true; + lastFrameAutoTween = true; + additiveBlending = false; + weight = 1; + fadeOutTime = fadeTotalTime; + + updateTimelineStates(); + return this; + } + + /** + * Fade out the animation state + * @param fadeOutTime + * @param pauseBeforeFadeOutComplete pause the animation before fade out complete + */ + public function fadeOut(fadeTotalTime:Number, pausePlayhead:Boolean):AnimationState + { + if(!_armature) + { + return null; + } + + if(isNaN(fadeTotalTime) || fadeTotalTime < 0) + { + fadeTotalTime = 0; + } + _pausePlayheadInFade = pausePlayhead; + + if(_isFadeOut) + { + if(fadeTotalTime > _fadeTotalTime / _timeScale - (_fadeCurrentTime - _fadeBeginTime)) + { + //如果已经在淡出中,新的淡出需要更长的淡出时间,则忽略 + //If the animation is already in fade out, the new fade out will be ignored. + return this; + } + } + else + { + //第一次淡出 + //The first time to fade out. + for each(var timelineState:TimelineState in _timelineStateList) + { + timelineState.fadeOut(); + } + } + + //fade start + _isFadeOut = true; + _fadeTotalWeight = _fadeWeight; + _fadeState = -1; + _fadeBeginTime = _fadeCurrentTime; + _fadeTotalTime = _fadeTotalWeight >= 0?fadeTotalTime * _timeScale:0; + + //default + displayControl = false; + + return this; + } + + /** @private */ + dragonBones_internal function advanceTime(passedTime:Number):Boolean + { + passedTime *= _timeScale; + + advanceFadeTime(passedTime); + + if(_fadeWeight) + { + advanceTimelinesTime(passedTime); + } + + return _isFadeOut && _fadeState == 1; + } + + private function advanceFadeTime(passedTime:Number):void + { + var fadeStartFlg:Boolean = false; + var fadeCompleteFlg:Boolean = false; + + if(_fadeBeginTime >= 0) + { + var fadeState:int = _fadeState; + _fadeCurrentTime += passedTime < 0?-passedTime:passedTime; + if(_fadeCurrentTime >= _fadeBeginTime + _fadeTotalTime) + { + //fade完全结束之后触发 + //TODO 研究明白为什么要下次再触发 + if( + _fadeWeight == 1 || + _fadeWeight == 0 + ) + { + fadeState = 1; + if (_pausePlayheadInFade) + { + _pausePlayheadInFade = false; + _currentTime = -1; + } + } + + _fadeWeight = _isFadeOut?0:1; + } + else if(_fadeCurrentTime >= _fadeBeginTime) + { + //fading + fadeState = 0; + //暂时只支持线性淡入淡出 + //Currently only support Linear fadein and fadeout + _fadeWeight = (_fadeCurrentTime - _fadeBeginTime) / _fadeTotalTime * _fadeTotalWeight; + if(_isFadeOut) + { + _fadeWeight = _fadeTotalWeight - _fadeWeight; + } + } + else + { + //before fade + fadeState = -1; + _fadeWeight = _isFadeOut?1:0; + } + + if(_fadeState != fadeState) + { + //_fadeState == -1 && (fadeState == 0 || fadeState == 1) + if(_fadeState == -1) + { + fadeStartFlg = true; + } + + //(_fadeState == -1 || _fadeState == 0) && fadeState == 1 + if(fadeState == 1) + { + fadeCompleteFlg = true; + } + _fadeState = fadeState; + } + } + + var event:AnimationEvent; + + if(fadeStartFlg) + { + if(_isFadeOut) + { + if(_armature.hasEventListener(AnimationEvent.FADE_OUT)) + { + event = new AnimationEvent(AnimationEvent.FADE_OUT); + event.animationState = this; + _armature._eventList.push(event); + } + } + else + { + //动画开始,先隐藏不需要的骨头 + hideBones(); + + if(_armature.hasEventListener(AnimationEvent.FADE_IN)) + { + event = new AnimationEvent(AnimationEvent.FADE_IN); + event.animationState = this; + _armature._eventList.push(event); + } + } + } + + if(fadeCompleteFlg) + { + if(_isFadeOut) + { + if(_armature.hasEventListener(AnimationEvent.FADE_OUT_COMPLETE)) + { + event = new AnimationEvent(AnimationEvent.FADE_OUT_COMPLETE); + event.animationState = this; + _armature._eventList.push(event); + } + } + else + { + if(_armature.hasEventListener(AnimationEvent.FADE_IN_COMPLETE)) + { + event = new AnimationEvent(AnimationEvent.FADE_IN_COMPLETE); + event.animationState = this; + _armature._eventList.push(event); + } + } + } + } + + private function advanceTimelinesTime(passedTime:Number):void + { + if(_isPlaying && !_pausePlayheadInFade) + { + _time += passedTime; + } + + var startFlg:Boolean = false; + var completeFlg:Boolean = false; + var loopCompleteFlg:Boolean = false; + var isThisComplete:Boolean = false; + var currentPlayTimes:int = 0; + var currentTime:int = _time * 1000; + if(_playTimes == 0) + { + isThisComplete = false; + currentPlayTimes = Math.ceil(Math.abs(currentTime) / _totalTime) || 1; + //currentTime -= Math.floor(currentTime / _totalTime) * _totalTime; + + currentTime -= int(currentTime / _totalTime) * _totalTime; + + if(currentTime < 0) + { + currentTime += _totalTime; + } + } + else + { + var totalTimes:int = _playTimes * _totalTime; + if(currentTime >= totalTimes) + { + currentTime = totalTimes; + isThisComplete = true; + } + else if(currentTime <= -totalTimes) + { + currentTime = -totalTimes; + isThisComplete = true; + } + else + { + isThisComplete = false; + } + + if(currentTime < 0) + { + currentTime += totalTimes; + } + + currentPlayTimes = Math.ceil(currentTime / _totalTime) || 1; + //currentTime -= Math.floor(currentTime / _totalTime) * _totalTime; + currentTime -= int(currentTime / _totalTime) * _totalTime; + + if(isThisComplete) + { + currentTime = _totalTime; + } + } + + //update timeline + _isComplete = isThisComplete; + var progress:Number = _time * 1000 / _totalTime; + for each(var timeline:TimelineState in _timelineStateList) + { + timeline.update(progress); + _isComplete = timeline._isComplete && _isComplete; + } + //update slotTimelie + for each(var slotTimeline:SlotTimelineState in _slotTimelineStateList) + { + slotTimeline.update(progress); + _isComplete = slotTimeline._isComplete && _isComplete; + } + //update main timeline + if(_currentTime != currentTime) + { + if(_currentPlayTimes != currentPlayTimes) //check loop complete + { + if(_currentPlayTimes > 0 && currentPlayTimes > 1) + { + loopCompleteFlg = true; + } + _currentPlayTimes = currentPlayTimes; + } + + if(_currentTime < 0) //check start + { + startFlg = true; + } + + if(_isComplete) //check complete + { + completeFlg = true; + } + _lastTime = _currentTime; + _currentTime = currentTime; + /* + if(isThisComplete) + { + currentTime = _totalTime * 0.999999; + } + //[0, _totalTime) + */ + updateMainTimeline(isThisComplete); + } + + var event:AnimationEvent; + if(startFlg) + { + if(_armature.hasEventListener(AnimationEvent.START)) + { + event = new AnimationEvent(AnimationEvent.START); + event.animationState = this; + _armature._eventList.push(event); + } + } + + if(completeFlg) + { + if(_armature.hasEventListener(AnimationEvent.COMPLETE)) + { + event = new AnimationEvent(AnimationEvent.COMPLETE); + event.animationState = this; + _armature._eventList.push(event); + } + if(autoFadeOut) + { + fadeOut(fadeOutTime, true); + } + } + else if(loopCompleteFlg) + { + if(_armature.hasEventListener(AnimationEvent.LOOP_COMPLETE)) + { + event = new AnimationEvent(AnimationEvent.LOOP_COMPLETE); + event.animationState = this; + _armature._eventList.push(event); + } + } + } + + private function updateMainTimeline(isThisComplete:Boolean):void + { + var frameList:Vector. = _clip.frameList; + if(frameList.length > 0) + { + var prevFrame:Frame; + var currentFrame:Frame; + for (var i:int = 0, l:int = _clip.frameList.length; i < l; ++i) + { + if(_currentFrameIndex < 0) + { + _currentFrameIndex = 0; + } + else if(_currentTime < _currentFramePosition || _currentTime >= _currentFramePosition + _currentFrameDuration || _currentTime < _lastTime) + { + _lastTime = _currentTime; + _currentFrameIndex ++; + if(_currentFrameIndex >= frameList.length) + { + if(isThisComplete) + { + _currentFrameIndex --; + break; + } + else + { + _currentFrameIndex = 0; + } + } + } + else + { + break; + } + currentFrame = frameList[_currentFrameIndex]; + + if(prevFrame) + { + _armature.arriveAtFrame(prevFrame, null, this, true); + } + + _currentFrameDuration = currentFrame.duration; + _currentFramePosition = currentFrame.position; + prevFrame = currentFrame; + } + + if(currentFrame) + { + _armature.arriveAtFrame(currentFrame, null, this, false); + } + } + } + + private function hideBones():void + { + for each(var timelineName:String in _clip.hideTimelineNameMap) + { + var bone:Bone = _armature.getBone(timelineName); + if(bone) + { + bone.hideSlots(); + } + } + } + + //属性访问 + public function setAdditiveBlending(value:Boolean):AnimationState + { + additiveBlending = value; + return this; + } + + + public function setAutoFadeOut(value:Boolean, fadeOutTime:Number = -1):AnimationState + { + autoFadeOut = value; + if(fadeOutTime >= 0) + { + this.fadeOutTime = fadeOutTime * _timeScale; + } + return this; + } + + public function setWeight(value:Number):AnimationState + { + if(isNaN(value) || value < 0) + { + value = 1; + } + weight = value; + return this; + } + + public function setFrameTween(autoTween:Boolean, lastFrameAutoTween:Boolean):AnimationState + { + this.autoTween = autoTween; + this.lastFrameAutoTween = lastFrameAutoTween; + return this; + } + + public function setCurrentTime(value:Number):AnimationState + { + if(value < 0 || isNaN(value)) + { + value = 0; + } + _time = value; + _currentTime = _time * 1000; + return this; + } + + public function setTimeScale(value:Number):AnimationState + { + if(isNaN(value) || value == Infinity) + { + value = 1; + } + _timeScale = value; + return this; + } + + public function setPlayTimes(value:int):AnimationState + { + //如果动画只有一帧 播放一次就可以 + if(Math.round(_totalTime * 0.001 * _clip.frameRate) < 2) + { + _playTimes = value < 0?-1:1; + } + else + { + _playTimes = value < 0?-value:value; + } + autoFadeOut = value < 0?true:false; + return this; + } + + /** + * The name of the animation state. + */ + public function get name():String + { + return _name; + } + + /** + * The layer of the animation. When calculating the final blend weights, animations in higher layers will get their weights. + */ + public function get layer():int + { + return _layer; + } + + /** + * The group of the animation. + */ + public function get group():String + { + return _group; + } + + /** + * The clip that is being played by this animation state. + * @see dragonBones.objects.AnimationData. + */ + public function get clip():AnimationData + { + return _clip; + } + + /** + * Is animation complete. + */ + public function get isComplete():Boolean + { + return _isComplete; + } + /** + * Is animation playing. + */ + public function get isPlaying():Boolean + { + return (_isPlaying && !_isComplete); + } + + /** + * Current animation played times + */ + public function get currentPlayTimes():int + { + return _currentPlayTimes < 0 ? 0 : _currentPlayTimes; + } + + /** + * The length of the animation clip in seconds. + */ + public function get totalTime():Number + { + return _totalTime * 0.001; + } + + /** + * The current time of the animation. + */ + public function get currentTime():Number + { + return _currentTime < 0 ? 0 : _currentTime * 0.001; + } + + public function get fadeWeight():Number + { + return _fadeWeight; + } + + public function get fadeState():int + { + return _fadeState; + } + + public function get fadeTotalTime():Number + { + return _fadeTotalTime; + } + + /** + * The amount by which passed time should be scaled. Used to slow down or speed up the animation. Defaults to 1. + */ + public function get timeScale():Number + { + return _timeScale; + } + + /** + * playTimes Play times(0:loop forever, 1~+∞:play times, -1~-∞:will fade animation after play complete). + */ + public function get playTimes():int + { + return _playTimes; + } + } +} diff --git a/srclib/dragonBones/animation/IAnimatable.as b/srclib/dragonBones/animation/IAnimatable.as new file mode 100644 index 00000000..7adcf650 --- /dev/null +++ b/srclib/dragonBones/animation/IAnimatable.as @@ -0,0 +1,23 @@ +package dragonBones.animation +{ + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0 + * @langversion 3.0 + * @version 2.0 + */ + + /** + * The IAnimatable interface defines the methods used by all animatable instance type used by the DragonBones system. + * @see dragonBones.Armature + * @see dragonBones.animation.WorldClock + */ + public interface IAnimatable + { + /** + * Update the animation using this method typically in an ENTERFRAME Event or with a Timer. + * @param The amount of second to move the playhead ahead. + */ + function advanceTime(passedTime:Number):void; + } +} \ No newline at end of file diff --git a/srclib/dragonBones/animation/SlotTimelineState.as b/srclib/dragonBones/animation/SlotTimelineState.as new file mode 100644 index 00000000..459adc97 --- /dev/null +++ b/srclib/dragonBones/animation/SlotTimelineState.as @@ -0,0 +1,552 @@ +package dragonBones.animation { + + import dragonBones.Armature; + import dragonBones.Slot; + import dragonBones.core.dragonBones_internal; + import dragonBones.objects.CurveData; + import dragonBones.objects.Frame; + import dragonBones.objects.SlotFrame; + import dragonBones.objects.SlotTimeline; + import dragonBones.utils.MathUtil; + + import flash.geom.ColorTransform; + + use namespace dragonBones_internal; + + /** @private */ + public final class SlotTimelineState + { + private static const HALF_PI:Number = Math.PI * 0.5; + private static const DOUBLE_PI:Number = Math.PI * 2; + + private static var _pool:Vector. = new Vector.; + + /** @private */ + dragonBones_internal static function borrowObject():SlotTimelineState + { + if(_pool.length == 0) + { + return new SlotTimelineState(); + } + return _pool.pop(); + } + + /** @private */ + dragonBones_internal static function returnObject(timeline:SlotTimelineState):void + { + if(_pool.indexOf(timeline) < 0) + { + _pool[_pool.length] = timeline; + } + + timeline.clear(); + } + + /** @private */ + dragonBones_internal static function clear():void + { + var i:int = _pool.length; + while(i --) + { + _pool[i].clear(); + } + _pool.length = 0; + } + + public var name:String; + + /** @private */ + dragonBones_internal var _weight:Number; + + //TO DO 干什么用的 + /** @private */ + dragonBones_internal var _blendEnabled:Boolean; + + /** @private */ + dragonBones_internal var _isComplete:Boolean; + + /** @private */ + dragonBones_internal var _animationState:AnimationState; + + private var _totalTime:int; //duration + + private var _currentTime:int; + private var _currentFrameIndex:int; + private var _currentFramePosition:int; + private var _currentFrameDuration:int; + + private var _tweenEasing:Number; + private var _tweenCurve:CurveData; + private var _tweenColor:Boolean; + + private var _rawAnimationScale:Number; + + //-1: frameLength>1, 0:frameLength==0, 1:frameLength==1 + private var _updateMode:int; + + private var _armature:Armature; + private var _animation:Animation; + private var _slot:Slot; + + private var _timelineData:SlotTimeline; + private var _durationColor:ColorTransform; + + + public function SlotTimelineState() + { + _durationColor = new ColorTransform(); + } + + private function clear():void + { +// if(_slot) +// { +// _slot.removeState(this); +// _slot = null; +// } + _slot = null; + _armature = null; + _animation = null; + _animationState = null; + _timelineData = null; + } + + //动画开始结束 + /** @private */ + dragonBones_internal function fadeIn(slot:Slot, animationState:AnimationState, timelineData:SlotTimeline):void + { + _slot = slot; + _armature = _slot.armature; + _animation = _armature.animation; + _animationState = animationState; + _timelineData = timelineData; + + name = timelineData.name; + + _totalTime = _timelineData.duration; + _rawAnimationScale = _animationState.clip.scale; + + _isComplete = false; + _blendEnabled = false; + _tweenColor = false; + _currentFrameIndex = -1; + _currentTime = -1; + _tweenEasing = NaN; + _weight = 1; + + switch(_timelineData.frameList.length) + { + case 0: + _updateMode = 0; + break; + + case 1: + _updateMode = 1; + break; + + default: + _updateMode = -1; + break; + } + +// _slot.addState(this); + } + + //动画进行中 + + /** @private */ + dragonBones_internal function update(progress:Number):void + { + if(_updateMode == -1) + { + updateMultipleFrame(progress); + } + else if(_updateMode == 1) + { + _updateMode = 0; + updateSingleFrame(); + } + } + + private function updateMultipleFrame(progress:Number):void + { + var currentPlayTimes:int = 0; + progress /= _timelineData.scale; + progress += _timelineData.offset; + + var currentTime:int = _totalTime * progress; + var playTimes:int = _animationState.playTimes; + if(playTimes == 0) + { + _isComplete = false; + currentPlayTimes = Math.ceil(Math.abs(currentTime) / _totalTime) || 1; + currentTime -= int(currentTime / _totalTime) * _totalTime; + + if(currentTime < 0) + { + currentTime += _totalTime; + } + } + else + { + var totalTimes:int = playTimes * _totalTime; + if(currentTime >= totalTimes) + { + currentTime = totalTimes; + _isComplete = true; + } + else if(currentTime <= -totalTimes) + { + currentTime = -totalTimes; + _isComplete = true; + } + else + { + _isComplete = false; + } + + if(currentTime < 0) + { + currentTime += totalTimes; + } + + currentPlayTimes = Math.ceil(currentTime / _totalTime) || 1; + if(_isComplete) + { + currentTime = _totalTime; + } + else + { + currentTime -= int(currentTime / _totalTime) * _totalTime; + } + } + + if(_currentTime != currentTime) + { + _currentTime = currentTime; + + var frameList:Vector. = _timelineData.frameList; + var prevFrame:SlotFrame; + var currentFrame:SlotFrame; + + for (var i:int = 0, l:int = _timelineData.frameList.length; i < l; ++i) + { + if(_currentFrameIndex < 0) + { + _currentFrameIndex = 0; + } + else if(_currentTime < _currentFramePosition || _currentTime >= _currentFramePosition + _currentFrameDuration) + { + _currentFrameIndex ++; + if(_currentFrameIndex >= frameList.length) + { + if(_isComplete) + { + _currentFrameIndex --; + break; + } + else + { + _currentFrameIndex = 0; + } + } + } + else + { + break; + } + currentFrame = frameList[_currentFrameIndex] as SlotFrame; + + if(prevFrame) + { + _slot.arriveAtFrame(prevFrame, this, _animationState, true); + } + + _currentFrameDuration = currentFrame.duration; + _currentFramePosition = currentFrame.position; + prevFrame = currentFrame; + } + + if(currentFrame) + { + _slot.arriveAtFrame(currentFrame, this, _animationState, false); + + _blendEnabled = currentFrame.displayIndex >= 0; + if(_blendEnabled) + { + updateToNextFrame(currentPlayTimes); + } + else + { + _tweenEasing = NaN; + _tweenColor = false; + } + } + + if(_blendEnabled) + { + updateTween(); + } + } + } + + private function updateToNextFrame(currentPlayTimes:int):void + { + var nextFrameIndex:int = _currentFrameIndex + 1; + if(nextFrameIndex >= _timelineData.frameList.length) + { + nextFrameIndex = 0; + } + var currentFrame:SlotFrame = _timelineData.frameList[_currentFrameIndex] as SlotFrame; + var nextFrame:SlotFrame = _timelineData.frameList[nextFrameIndex] as SlotFrame; + var tweenEnabled:Boolean = false; + if( + nextFrameIndex == 0 && + ( + !_animationState.lastFrameAutoTween || + ( + _animationState.playTimes && + _animationState.currentPlayTimes >= _animationState.playTimes && + ((_currentFramePosition + _currentFrameDuration) / _totalTime + currentPlayTimes - _timelineData.offset) * _timelineData.scale > 0.999999 + ) + ) + ) + { + _tweenEasing = NaN; + tweenEnabled = false; + } + else if(currentFrame.displayIndex < 0 || nextFrame.displayIndex < 0) + { + _tweenEasing = NaN; + tweenEnabled = false; + } + else if(_animationState.autoTween) + { + _tweenEasing = _animationState.clip.tweenEasing; + if(isNaN(_tweenEasing)) + { + _tweenEasing = currentFrame.tweenEasing; + _tweenCurve = currentFrame.curve; + if(isNaN(_tweenEasing) && _tweenCurve == null) //frame no tween + { + tweenEnabled = false; + } + else + { + if(_tweenEasing == 10) + { + _tweenEasing = 0; + } + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + else //animationData overwrite tween + { + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + else + { + _tweenEasing = currentFrame.tweenEasing; + _tweenCurve = currentFrame.curve; + if((isNaN(_tweenEasing) || _tweenEasing == 10) && _tweenCurve == null) //frame no tween + { + _tweenEasing = NaN; + tweenEnabled = false; + } + else + { + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + + if(tweenEnabled) + { + if(currentFrame.color && nextFrame.color) + { + _durationColor.alphaOffset = nextFrame.color.alphaOffset - currentFrame.color.alphaOffset; + _durationColor.redOffset = nextFrame.color.redOffset - currentFrame.color.redOffset; + _durationColor.greenOffset = nextFrame.color.greenOffset - currentFrame.color.greenOffset; + _durationColor.blueOffset = nextFrame.color.blueOffset - currentFrame.color.blueOffset; + + _durationColor.alphaMultiplier = nextFrame.color.alphaMultiplier - currentFrame.color.alphaMultiplier; + _durationColor.redMultiplier = nextFrame.color.redMultiplier - currentFrame.color.redMultiplier; + _durationColor.greenMultiplier = nextFrame.color.greenMultiplier - currentFrame.color.greenMultiplier; + _durationColor.blueMultiplier = nextFrame.color.blueMultiplier - currentFrame.color.blueMultiplier; + + if( + _durationColor.alphaOffset || + _durationColor.redOffset || + _durationColor.greenOffset || + _durationColor.blueOffset || + _durationColor.alphaMultiplier || + _durationColor.redMultiplier || + _durationColor.greenMultiplier || + _durationColor.blueMultiplier + ) + { + _tweenColor = true; + } + else + { + _tweenColor = false; + } + } + else if(currentFrame.color) + { + _tweenColor = true; + _durationColor.alphaOffset = -currentFrame.color.alphaOffset; + _durationColor.redOffset = -currentFrame.color.redOffset; + _durationColor.greenOffset = -currentFrame.color.greenOffset; + _durationColor.blueOffset = -currentFrame.color.blueOffset; + + _durationColor.alphaMultiplier = 1 - currentFrame.color.alphaMultiplier; + _durationColor.redMultiplier = 1 - currentFrame.color.redMultiplier; + _durationColor.greenMultiplier = 1 - currentFrame.color.greenMultiplier; + _durationColor.blueMultiplier = 1 - currentFrame.color.blueMultiplier; + } + else if(nextFrame.color) + { + _tweenColor = true; + _durationColor.alphaOffset = nextFrame.color.alphaOffset; + _durationColor.redOffset = nextFrame.color.redOffset; + _durationColor.greenOffset = nextFrame.color.greenOffset; + _durationColor.blueOffset = nextFrame.color.blueOffset; + + _durationColor.alphaMultiplier = nextFrame.color.alphaMultiplier - 1; + _durationColor.redMultiplier = nextFrame.color.redMultiplier - 1; + _durationColor.greenMultiplier = nextFrame.color.greenMultiplier - 1; + _durationColor.blueMultiplier = nextFrame.color.blueMultiplier - 1; + } + else + { + _tweenColor = false; + } + } + else + { + _tweenColor = false; + } + + if(!_tweenColor && _animationState.displayControl) + { + if(currentFrame.color) + { + _slot.updateDisplayColor( + currentFrame.color.alphaOffset, + currentFrame.color.redOffset, + currentFrame.color.greenOffset, + currentFrame.color.blueOffset, + currentFrame.color.alphaMultiplier, + currentFrame.color.redMultiplier, + currentFrame.color.greenMultiplier, + currentFrame.color.blueMultiplier, + true + ); + } + else if(_slot._isColorChanged) + { + _slot.updateDisplayColor(0, 0, 0, 0, 1, 1, 1, 1, false); + } + + } + } + + private function updateTween():void + { + var currentFrame:SlotFrame = _timelineData.frameList[_currentFrameIndex] as SlotFrame; + + if(_tweenColor && _animationState.displayControl) + { + var progress:Number = (_currentTime - _currentFramePosition) / _currentFrameDuration; + if (_tweenCurve != null) + { + progress = _tweenCurve.getValueByProgress(progress); + } + if(_tweenEasing) + { + progress = MathUtil.getEaseValue(progress, _tweenEasing); + } + + if(currentFrame.color) + { + _slot.updateDisplayColor( + currentFrame.color.alphaOffset + _durationColor.alphaOffset * progress, + currentFrame.color.redOffset + _durationColor.redOffset * progress, + currentFrame.color.greenOffset + _durationColor.greenOffset * progress, + currentFrame.color.blueOffset + _durationColor.blueOffset * progress, + currentFrame.color.alphaMultiplier + _durationColor.alphaMultiplier * progress, + currentFrame.color.redMultiplier + _durationColor.redMultiplier * progress, + currentFrame.color.greenMultiplier + _durationColor.greenMultiplier * progress, + currentFrame.color.blueMultiplier + _durationColor.blueMultiplier * progress, + true + ); + } + else + { + _slot.updateDisplayColor( + _durationColor.alphaOffset * progress, + _durationColor.redOffset * progress, + _durationColor.greenOffset * progress, + _durationColor.blueOffset * progress, + 1 + _durationColor.alphaMultiplier * progress, + 1 + _durationColor.redMultiplier * progress, + 1 + _durationColor.greenMultiplier * progress, + 1 + _durationColor.blueMultiplier * progress, + true + ); + } + } + } + + private function updateSingleFrame():void + { + var currentFrame:SlotFrame = _timelineData.frameList[0] as SlotFrame; + _slot.arriveAtFrame(currentFrame, this, _animationState, false); + _isComplete = true; + _tweenEasing = NaN; + _tweenColor = false; + + _blendEnabled = currentFrame.displayIndex >= 0; + if(_blendEnabled) + { + /** + * <使用绝对数据> + * 单帧的timeline,第一个关键帧的transform为0 + * timeline.originTransform = firstFrame.transform; + * eachFrame.transform = eachFrame.transform - timeline.originTransform; + * firstFrame.transform == 0; + * + * <使用相对数据> + * 使用相对数据时,timeline.originTransform = 0,第一个关键帧的transform有可能不为 0 + */ + if(_animationState.displayControl) + { + if(currentFrame.color) + { + _slot.updateDisplayColor( + currentFrame.color.alphaOffset, + currentFrame.color.redOffset, + currentFrame.color.greenOffset, + currentFrame.color.blueOffset, + currentFrame.color.alphaMultiplier, + currentFrame.color.redMultiplier, + currentFrame.color.greenMultiplier, + currentFrame.color.blueMultiplier, + true + ); + } + else if(_slot._isColorChanged) + { + _slot.updateDisplayColor(0, 0, 0, 0, 1, 1, 1, 1, false); + } + } + } + } + + + } +} diff --git a/srclib/dragonBones/animation/TimelineState.as b/srclib/dragonBones/animation/TimelineState.as new file mode 100644 index 00000000..8b192aeb --- /dev/null +++ b/srclib/dragonBones/animation/TimelineState.as @@ -0,0 +1,613 @@ +package dragonBones.animation { + + import dragonBones.Armature; + import dragonBones.Bone; + import dragonBones.core.dragonBones_internal; + import dragonBones.objects.CurveData; + import dragonBones.objects.DBTransform; + import dragonBones.objects.Frame; + import dragonBones.objects.TransformFrame; + import dragonBones.objects.TransformTimeline; + import dragonBones.utils.MathUtil; + import dragonBones.utils.TransformUtil; + + import flash.geom.Point; + + use namespace dragonBones_internal; + + /** @private */ + public final class TimelineState + { + private static const HALF_PI:Number = Math.PI * 0.5; + private static const DOUBLE_PI:Number = Math.PI * 2; + + private static var _pool:Vector. = new Vector.; + + /** @private */ + dragonBones_internal static function borrowObject():TimelineState + { + if(_pool.length == 0) + { + return new TimelineState(); + } + return _pool.pop(); + } + + /** @private */ + dragonBones_internal static function returnObject(timeline:TimelineState):void + { + if(_pool.indexOf(timeline) < 0) + { + _pool[_pool.length] = timeline; + } + + timeline.clear(); + } + + /** @private */ + dragonBones_internal static function clear():void + { + var i:int = _pool.length; + while(i --) + { + _pool[i].clear(); + } + _pool.length = 0; + } + + public var name:String; + + /** @private */ + dragonBones_internal var _weight:Number; + + /** @private */ + dragonBones_internal var _transform:DBTransform; + + /** @private */ + dragonBones_internal var _pivot:Point; + + //TO DO 干什么用的 + /** @private */ + dragonBones_internal var _blendEnabled:Boolean; + + /** @private */ + dragonBones_internal var _isComplete:Boolean; + + /** @private */ + dragonBones_internal var _animationState:AnimationState; + + private var _totalTime:int; //duration + + private var _currentTime:int; + private var _lastTime:int; + private var _currentFrameIndex:int; + private var _currentFramePosition:int; + private var _currentFrameDuration:int; + + private var _tweenEasing:Number; + private var _tweenCurve:CurveData; + private var _tweenTransform:Boolean; + private var _tweenScale:Boolean; + + private var _rawAnimationScale:Number; + + //-1: frameLength>1, 0:frameLength==0, 1:frameLength==1 + private var _updateMode:int; + + private var _armature:Armature; + private var _animation:Animation; + private var _bone:Bone; + + private var _timelineData:TransformTimeline; + private var _originTransform:DBTransform; + private var _originPivot:Point; + + private var _durationTransform:DBTransform; + private var _durationPivot:Point; + + + public function TimelineState() + { + _transform = new DBTransform(); + _pivot = new Point(); + + _durationTransform = new DBTransform(); + _durationPivot = new Point(); + } + + private function clear():void + { + if(_bone) + { + _bone.removeState(this); + _bone = null; + } + _armature = null; + _animation = null; + _animationState = null; + _timelineData = null; + _originTransform = null; + _originPivot = null; + } + + //动画开始结束 + /** @private */ + dragonBones_internal function fadeIn(bone:Bone, animationState:AnimationState, timelineData:TransformTimeline):void + { + _bone = bone; + _armature = _bone.armature; + _animation = _armature.animation; + _animationState = animationState; + _timelineData = timelineData; + _originTransform = _timelineData.originTransform; + _originPivot = _timelineData.originPivot; + + name = timelineData.name; + + _totalTime = _timelineData.duration; + _rawAnimationScale = _animationState.clip.scale; + + _isComplete = false; + _blendEnabled = false; + _tweenTransform = false; + _tweenScale = false; + _currentFrameIndex = -1; + _currentTime = -1; + _tweenEasing = NaN; + _weight = 1; + + _transform.x = 0; + _transform.y = 0; + _transform.scaleX = 1; + _transform.scaleY = 1; + _transform.skewX = 0; + _transform.skewY = 0; + _pivot.x = 0; + _pivot.y = 0; + + _durationTransform.x = 0; + _durationTransform.y = 0; + _durationTransform.scaleX = 1; + _durationTransform.scaleY = 1; + _durationTransform.skewX = 0; + _durationTransform.skewY = 0; + _durationPivot.x = 0; + _durationPivot.y = 0; + + switch(_timelineData.frameList.length) + { + case 0: + _updateMode = 0; + break; + + case 1: + _updateMode = 1; + break; + + default: + _updateMode = -1; + break; + } + + _bone.addState(this); + } + + /** @private */ + dragonBones_internal function fadeOut():void + { + _transform.skewX = TransformUtil.formatRadian(_transform.skewX); + _transform.skewY = TransformUtil.formatRadian(_transform.skewY); + } + + //动画进行中 + + /** @private */ + dragonBones_internal function update(progress:Number):void + { + if(_updateMode == -1) + { + updateMultipleFrame(progress); + } + else if(_updateMode == 1) + { + _updateMode = 0; + updateSingleFrame(); + } + } + + private function updateMultipleFrame(progress:Number):void + { + var currentPlayTimes:int = 0; + progress /= _timelineData.scale; + progress += _timelineData.offset; + + var currentTime:int = _totalTime * progress; + var playTimes:int = _animationState.playTimes; + if(playTimes == 0) + { + _isComplete = false; + currentPlayTimes = Math.ceil(Math.abs(currentTime) / _totalTime) || 1; + currentTime -= int(currentTime / _totalTime) * _totalTime; + + if(currentTime < 0) + { + currentTime += _totalTime; + } + } + else + { + var totalTimes:int = playTimes * _totalTime; + if(currentTime >= totalTimes) + { + currentTime = totalTimes; + _isComplete = true; + } + else if(currentTime <= -totalTimes) + { + currentTime = -totalTimes; + _isComplete = true; + } + else + { + _isComplete = false; + } + + if(currentTime < 0) + { + currentTime += totalTimes; + } + + currentPlayTimes = Math.ceil(currentTime / _totalTime) || 1; + if(_isComplete) + { + currentTime = _totalTime; + } + else + { + currentTime -= int(currentTime / _totalTime) * _totalTime; + } + } + + if(_currentTime != currentTime) + { + _lastTime = _currentTime; + _currentTime = currentTime; + + var frameList:Vector. = _timelineData.frameList; + var prevFrame:TransformFrame; + var currentFrame:TransformFrame; + + for (var i:int = 0, l:int = _timelineData.frameList.length; i < l; ++i) + { + if(_currentFrameIndex < 0) + { + _currentFrameIndex = 0; + } + else if(_currentTime < _currentFramePosition || _currentTime >= _currentFramePosition + _currentFrameDuration || _currentTime < _lastTime) + { + _currentFrameIndex ++; + _lastTime = _currentTime; + if(_currentFrameIndex >= frameList.length) + { + if(_isComplete) + { + _currentFrameIndex --; + break; + } + else + { + _currentFrameIndex = 0; + } + } + } + else + { + break; + } + currentFrame = frameList[_currentFrameIndex] as TransformFrame; + + if(prevFrame) + { + _bone.arriveAtFrame(prevFrame, this, _animationState, true); + } + + _currentFrameDuration = currentFrame.duration; + _currentFramePosition = currentFrame.position; + prevFrame = currentFrame; + } + + if(currentFrame) + { + _bone.arriveAtFrame(currentFrame, this, _animationState, false); + + _blendEnabled = !isNaN(currentFrame.tweenEasing); + + if(_blendEnabled) + { + updateToNextFrame(currentPlayTimes); + } + else + { + _tweenEasing = NaN; + _tweenTransform = false; + _tweenScale = false; + } + } + + if(_blendEnabled) + { + updateTween(); + } + } + } + + private function updateToNextFrame(currentPlayTimes:int):void + { + var nextFrameIndex:int = _currentFrameIndex + 1; + if(nextFrameIndex >= _timelineData.frameList.length) + { + nextFrameIndex = 0; + } + var currentFrame:TransformFrame = _timelineData.frameList[_currentFrameIndex] as TransformFrame; + var nextFrame:TransformFrame = _timelineData.frameList[nextFrameIndex] as TransformFrame; + var tweenEnabled:Boolean = false; + if( + nextFrameIndex == 0 && + ( + !_animationState.lastFrameAutoTween || + ( + _animationState.playTimes && + _animationState.currentPlayTimes >= _animationState.playTimes && + ((_currentFramePosition + _currentFrameDuration) / _totalTime + currentPlayTimes - _timelineData.offset) * _timelineData.scale > 0.999999 + ) + ) + ) + { + _tweenEasing = NaN; + tweenEnabled = false; + } +// else if(currentFrame.displayIndex < 0 || nextFrame.displayIndex < 0) +// { +// _tweenEasing = NaN; +// tweenEnabled = false; +// } + else if(_animationState.autoTween) + { + _tweenEasing = _animationState.clip.tweenEasing; + if(isNaN(_tweenEasing)) + { + _tweenEasing = currentFrame.tweenEasing; + _tweenCurve = currentFrame.curve; + if(isNaN(_tweenEasing) && _tweenCurve == null) //frame no tween + { + tweenEnabled = false; + } + else + { + if(_tweenEasing == 10) + { + _tweenEasing = 0; + } + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + else //animationData overwrite tween + { + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + else + { + _tweenEasing = currentFrame.tweenEasing; + _tweenCurve = currentFrame.curve; + if((isNaN(_tweenEasing) || _tweenEasing == 10) && _tweenCurve == null) //frame no tween + { + _tweenEasing = NaN; + tweenEnabled = false; + } + else + { + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + + if(tweenEnabled) + { + //transform + _durationTransform.x = nextFrame.transform.x - currentFrame.transform.x; + _durationTransform.y = nextFrame.transform.y - currentFrame.transform.y; + _durationTransform.skewX = nextFrame.transform.skewX - currentFrame.transform.skewX; + _durationTransform.skewY = nextFrame.transform.skewY - currentFrame.transform.skewY; + + _durationTransform.scaleX = nextFrame.transform.scaleX - currentFrame.transform.scaleX + nextFrame.scaleOffset.x; + _durationTransform.scaleY = nextFrame.transform.scaleY - currentFrame.transform.scaleY + nextFrame.scaleOffset.y; + _durationTransform.normalizeRotation(); + if(nextFrameIndex == 0) + { + _durationTransform.skewX = TransformUtil.formatRadian(_durationTransform.skewX); + _durationTransform.skewY = TransformUtil.formatRadian(_durationTransform.skewY); + } + + _durationPivot.x = nextFrame.pivot.x - currentFrame.pivot.x; + _durationPivot.y = nextFrame.pivot.y - currentFrame.pivot.y; + + if( + _durationTransform.x || + _durationTransform.y || + _durationTransform.skewX || + _durationTransform.skewY || + _durationTransform.scaleX || + _durationTransform.scaleY || + _durationPivot.x || + _durationPivot.y + ) + { + _tweenTransform = true; + _tweenScale = currentFrame.tweenScale; + } + else + { + _tweenTransform = false; + _tweenScale = false; + } + + } + else + { + _tweenTransform = false; + _tweenScale = false; + } + + if(!_tweenTransform) + { + if(_animationState.additiveBlending) + { + _transform.x = currentFrame.transform.x; + _transform.y = currentFrame.transform.y; + _transform.skewX = currentFrame.transform.skewX; + _transform.skewY = currentFrame.transform.skewY; + _transform.scaleX = currentFrame.transform.scaleX; + _transform.scaleY = currentFrame.transform.scaleY; + + _pivot.x = currentFrame.pivot.x; + _pivot.y = currentFrame.pivot.y; + } + else + { + _transform.x = _originTransform.x + currentFrame.transform.x; + _transform.y = _originTransform.y + currentFrame.transform.y; + _transform.skewX = _originTransform.skewX + currentFrame.transform.skewX; + _transform.skewY = _originTransform.skewY + currentFrame.transform.skewY; + _transform.scaleX = _originTransform.scaleX * currentFrame.transform.scaleX; + _transform.scaleY = _originTransform.scaleY * currentFrame.transform.scaleY; + + _pivot.x = _originPivot.x + currentFrame.pivot.x; + _pivot.y = _originPivot.y + currentFrame.pivot.y; + } + + _bone.invalidUpdate(); + } + else if(!_tweenScale) + { + if(_animationState.additiveBlending) + { + _transform.scaleX = currentFrame.transform.scaleX; + _transform.scaleY = currentFrame.transform.scaleY; + } + else + { + _transform.scaleX = _originTransform.scaleX * currentFrame.transform.scaleX; + _transform.scaleY = _originTransform.scaleY * currentFrame.transform.scaleY; + } + } + } + + private function updateTween():void + { + var currentFrame:TransformFrame = _timelineData.frameList[_currentFrameIndex] as TransformFrame; + if(_tweenTransform) + { + var progress:Number = (_currentTime - _currentFramePosition) / _currentFrameDuration; + if (_tweenCurve != null) + { + progress = _tweenCurve.getValueByProgress(progress); + } + else if(_tweenEasing) + { + progress = MathUtil.getEaseValue(progress, _tweenEasing); + } + + var currentTransform:DBTransform = currentFrame.transform; + var currentPivot:Point = currentFrame.pivot; + if(_animationState.additiveBlending) + { + //additive blending + _transform.x = currentTransform.x + _durationTransform.x * progress; + _transform.y = currentTransform.y + _durationTransform.y * progress; + _transform.skewX = currentTransform.skewX + _durationTransform.skewX * progress; + _transform.skewY = currentTransform.skewY + _durationTransform.skewY * progress; + if(_tweenScale) + { + _transform.scaleX = currentTransform.scaleX + _durationTransform.scaleX * progress; + _transform.scaleY = currentTransform.scaleY + _durationTransform.scaleY * progress; + } + + _pivot.x = currentPivot.x + _durationPivot.x * progress; + _pivot.y = currentPivot.y + _durationPivot.y * progress; + } + else + { + //normal blending + _transform.x = _originTransform.x + currentTransform.x + _durationTransform.x * progress; + _transform.y = _originTransform.y + currentTransform.y + _durationTransform.y * progress; + _transform.skewX = _originTransform.skewX + currentTransform.skewX + _durationTransform.skewX * progress; + _transform.skewY = _originTransform.skewY + currentTransform.skewY + _durationTransform.skewY * progress; + if(_tweenScale) + { + _transform.scaleX = _originTransform.scaleX * currentTransform.scaleX + _durationTransform.scaleX * progress; + _transform.scaleY = _originTransform.scaleY * currentTransform.scaleY + _durationTransform.scaleY * progress; + } + + _pivot.x = _originPivot.x + currentPivot.x + _durationPivot.x * progress; + _pivot.y = _originPivot.y + currentPivot.y + _durationPivot.y * progress; + } + + _bone.invalidUpdate(); + } + } + + private function updateSingleFrame():void + { + var currentFrame:TransformFrame = _timelineData.frameList[0] as TransformFrame; + _bone.arriveAtFrame(currentFrame, this, _animationState, false); + _isComplete = true; + _tweenEasing = NaN; + _tweenTransform = false; + _tweenScale = false; + //_tweenColor = false; + + _blendEnabled = true; + /** + * <使用绝对数据> + * 单帧的timeline,第一个关键帧的transform为0 + * timeline.originTransform = firstFrame.transform; + * eachFrame.transform = eachFrame.transform - timeline.originTransform; + * firstFrame.transform == 0; + * + * <使用相对数据> + * 使用相对数据时,timeline.originTransform = 0,第一个关键帧的transform有可能不为 0 + */ + if(_animationState.additiveBlending) + { + _transform.x = currentFrame.transform.x; + _transform.y = currentFrame.transform.y; + _transform.skewX = currentFrame.transform.skewX; + _transform.skewY = currentFrame.transform.skewY; + _transform.scaleX = currentFrame.transform.scaleX; + _transform.scaleY = currentFrame.transform.scaleY; + + _pivot.x = currentFrame.pivot.x; + _pivot.y = currentFrame.pivot.y; + } + else + { + _transform.x = _originTransform.x + currentFrame.transform.x; + _transform.y = _originTransform.y + currentFrame.transform.y; + _transform.skewX = _originTransform.skewX + currentFrame.transform.skewX; + _transform.skewY = _originTransform.skewY + currentFrame.transform.skewY; + _transform.scaleX = _originTransform.scaleX * currentFrame.transform.scaleX; + _transform.scaleY = _originTransform.scaleY * currentFrame.transform.scaleY; + + _pivot.x = _originPivot.x + currentFrame.pivot.x; + _pivot.y = _originPivot.y + currentFrame.pivot.y; + } + + _bone.invalidUpdate(); + } + + + } +} diff --git a/srclib/dragonBones/animation/WorldClock.as b/srclib/dragonBones/animation/WorldClock.as new file mode 100644 index 00000000..38fe21ad --- /dev/null +++ b/srclib/dragonBones/animation/WorldClock.as @@ -0,0 +1,143 @@ +package dragonBones.animation { + + import flash.utils.getTimer; + + /** + * A WorldClock instance lets you conveniently update many number of Armature instances at once. You can add/remove Armature instance and set a global timescale that will apply to all registered Armature instance animations. + * @see dragonBones.Armature + * @see dragonBones.animation.Animation + */ + public final class WorldClock implements IAnimatable + { + /** + * A global static WorldClock instance ready to use. + */ + public static var clock:WorldClock = new WorldClock(); + + private var _animatableList:Vector.; + + private var _time:Number; + public function get time():Number + { + return _time; + } + + private var _timeScale:Number; + /** + * The time scale to apply to the number of second passed to the advanceTime() method. + * @param A Number to use as a time scale. + */ + public function get timeScale():Number + { + return _timeScale; + } + public function set timeScale(value:Number):void + { + if(isNaN(value) || value < 0) + { + value = 1; + } + _timeScale = value; + } + + /** + * Creates a new WorldClock instance. (use the static var WorldClock.clock instead). + */ + public function WorldClock(time:Number = -1, timeScale:Number = 1) + { + _time = time >= 0?time:getTimer() * 0.001; + _timeScale = isNaN(timeScale)?1:timeScale; + _animatableList = new Vector.; + } + + /** + * Returns true if the IAnimatable instance is contained by WorldClock instance. + * @param An IAnimatable instance (Armature or custom) + * @return true if the IAnimatable instance is contained by WorldClock instance. + */ + public function contains(animatable:IAnimatable):Boolean + { + return _animatableList.indexOf(animatable) >= 0; + } + + /** + * Add a IAnimatable instance (Armature or custom) to this WorldClock instance. + * @param An IAnimatable instance (Armature, WorldClock or custom) + */ + public function add(animatable:IAnimatable):void + { + if (animatable && _animatableList.indexOf(animatable) == -1) + { + _animatableList.push(animatable); + } + } + + /** + * Remove a IAnimatable instance (Armature or custom) from this WorldClock instance. + * @param An IAnimatable instance (Armature or custom) + */ + public function remove(animatable:IAnimatable):void + { + var index:int = _animatableList.indexOf(animatable); + if (index >= 0) + { + _animatableList[index] = null; + } + } + + /** + * Remove all IAnimatable instance (Armature or custom) from this WorldClock instance. + */ + public function clear():void + { + _animatableList.length = 0; + } + + /** + * Update all registered IAnimatable instance animations using this method typically in an ENTERFRAME Event or with a Timer. + * @param The amount of second to move the playhead ahead. + */ + public function advanceTime(passedTime:Number):void + { + if(passedTime < 0) + { + passedTime = getTimer() * 0.001 - _time; + } + _time += passedTime; + + passedTime *= _timeScale; + + var length:int = _animatableList.length; + if(length == 0) + { + return; + } + var currentIndex:int = 0; + + for(var i:int = 0;i < length; i++) + { + var animatable:IAnimatable = _animatableList[i]; + if(animatable) + { + if(currentIndex != i) + { + _animatableList[currentIndex] = animatable; + _animatableList[i] = null; + } + animatable.advanceTime(passedTime); + currentIndex ++; + } + } + + if (currentIndex != i) + { + length = _animatableList.length; + while(i < length) + { + _animatableList[currentIndex ++] = _animatableList[i ++]; + } + _animatableList.length = currentIndex; + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/cache/AnimationCache.as b/srclib/dragonBones/cache/AnimationCache.as new file mode 100644 index 00000000..51c08816 --- /dev/null +++ b/srclib/dragonBones/cache/AnimationCache.as @@ -0,0 +1,132 @@ +package dragonBones.cache { + + import dragonBones.objects.AnimationData; + import dragonBones.objects.ArmatureData; + import dragonBones.objects.BoneData; + import dragonBones.objects.SlotData; + import dragonBones.objects.TransformTimeline; + + public class AnimationCache + { + public var name:String; +// public var boneTimelineCacheList:Vector. = new Vector.(); + public var slotTimelineCacheList:Vector. = new Vector.(); +// public var boneTimelineCacheDic:Object = {}; + public var slotTimelineCacheDic:Object = {}; + public var frameNum:int = 0; + public function AnimationCache() + { + } + + public static function initWithAnimationData(animationData:AnimationData,armatureData:ArmatureData):AnimationCache + { + var output:AnimationCache = new AnimationCache(); + output.name = animationData.name; + + var boneTimelineList:Vector. = animationData.timelineList; + var boneName:String; + var boneData:BoneData; + var slotData:SlotData; + var slotTimelineCache:SlotTimelineCache; + var slotName:String; + + for(var i:int = 0, length:int = boneTimelineList.length; i < length; i++) + { + boneName = boneTimelineList[i].name; + for (var j:int = 0, jlen:int = armatureData.slotDataList.length; j < jlen; j++) + { + slotData = armatureData.slotDataList[j]; + slotName = slotData.name; + if (slotData.parent == boneName) + { + if (output.slotTimelineCacheDic[slotName] == null) + { + slotTimelineCache = new SlotTimelineCache(); + slotTimelineCache.name = slotName; + output.slotTimelineCacheList.push(slotTimelineCache); + output.slotTimelineCacheDic[slotName] = slotTimelineCache; + } + + } + } + } + return output; + } + +// public function initBoneTimelineCacheDic(boneCacheGeneratorDic:Object, boneFrameCacheDic:Object):void +// { +// var name:String; +// for each(var boneTimelineCache:BoneTimelineCache in boneTimelineCacheDic) +// { +// name = boneTimelineCache.name; +// boneTimelineCache.cacheGenerator = boneCacheGeneratorDic[name]; +// boneTimelineCache.currentFrameCache = boneFrameCacheDic[name]; +// } +// } + + public function initSlotTimelineCacheDic(slotCacheGeneratorDic:Object, slotFrameCacheDic:Object):void + { + var name:String; + for each(var slotTimelineCache:SlotTimelineCache in slotTimelineCacheDic) + { + name = slotTimelineCache.name; + slotTimelineCache.cacheGenerator = slotCacheGeneratorDic[name]; + slotTimelineCache.currentFrameCache = slotFrameCacheDic[name]; + } + } + +// public function bindCacheUserBoneDic(boneDic:Object):void +// { +// for(var name:String in boneDic) +// { +// (boneTimelineCacheDic[name] as BoneTimelineCache).bindCacheUser(boneDic[name]); +// } +// } + + public function bindCacheUserSlotDic(slotDic:Object):void + { + for(var name:String in slotDic) + { + (slotTimelineCacheDic[name] as SlotTimelineCache).bindCacheUser(slotDic[name]); + } + } + + public function addFrame():void + { + frameNum++; +// var boneTimelineCache:BoneTimelineCache; +// for(var i:int = 0, length:int = boneTimelineCacheList.length; i < length; i++) +// { +// boneTimelineCache = boneTimelineCacheList[i]; +// boneTimelineCache.addFrame(); +// } + + var slotTimelineCache:SlotTimelineCache; + for(var i:int = 0, length:int = slotTimelineCacheList.length; i < length; i++) + { + slotTimelineCache = slotTimelineCacheList[i]; + slotTimelineCache.addFrame(); + } + } + + + public function update(progress:Number):void + { + var frameIndex:int = progress * (frameNum-1); + +// var boneTimelineCache:BoneTimelineCache; +// for(var i:int = 0, length:int = boneTimelineCacheList.length; i < length; i++) +// { +// boneTimelineCache = boneTimelineCacheList[i]; +// boneTimelineCache.update(frameIndex); +// } + + var slotTimelineCache:SlotTimelineCache; + for(var i:int = 0, length:int = slotTimelineCacheList.length; i < length; i++) + { + slotTimelineCache = slotTimelineCacheList[i]; + slotTimelineCache.update(frameIndex); + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/cache/AnimationCacheManager.as b/srclib/dragonBones/cache/AnimationCacheManager.as new file mode 100644 index 00000000..77ac86d7 --- /dev/null +++ b/srclib/dragonBones/cache/AnimationCacheManager.as @@ -0,0 +1,163 @@ +package dragonBones.cache { + + import dragonBones.core.IAnimationState; + import dragonBones.core.ICacheUser; + import dragonBones.core.ICacheableArmature; + import dragonBones.core.dragonBones_internal; + import dragonBones.objects.AnimationData; + import dragonBones.objects.ArmatureData; + + use namespace dragonBones_internal; + + public class AnimationCacheManager + { + public var cacheGeneratorArmature:ICacheableArmature + public var armatureData:ArmatureData; + public var frameRate:Number; + public var animationCacheDic:Object = {}; +// public var boneFrameCacheDic:Object = {}; + public var slotFrameCacheDic:Object = {}; + public function AnimationCacheManager() + { + } + + public static function initWithArmatureData(armatureData:ArmatureData, frameRate:Number = 0):AnimationCacheManager + { + var output:AnimationCacheManager = new AnimationCacheManager(); + output.armatureData = armatureData; + if(frameRate<=0) + { + var animationData:AnimationData = armatureData.animationDataList[0]; + if(animationData) + { + output.frameRate = animationData.frameRate; + } + } + else + { + output.frameRate = frameRate; + } + + return output; + } + + public function initAllAnimationCache():void + { + for each(var animationData:AnimationData in armatureData.animationDataList) + { + animationCacheDic[animationData.name] = AnimationCache.initWithAnimationData(animationData,armatureData); + } + } + + public function initAnimationCache(animationName:String):void + { + animationCacheDic[animationName] = AnimationCache.initWithAnimationData(armatureData.getAnimationData(animationName),armatureData); + } + + public function bindCacheUserArmatures(armatures:Array):void + { + for each(var armature:ICacheableArmature in armatures) + { + bindCacheUserArmature(armature); + } + + } + + public function bindCacheUserArmature(armature:ICacheableArmature):void + { + armature.getAnimation().animationCacheManager = this; + + var slotDic:Object = armature.getSlotDic(); + var cacheUser:ICacheUser; +// for each(cacheUser in armature._boneDic) +// { +// cacheUser.frameCache = boneFrameCacheDic[cacheUser.name]; +// } + for each(cacheUser in slotDic) + { + cacheUser.frameCache = slotFrameCacheDic[cacheUser.name]; + } + } + + public function setCacheGeneratorArmature(armature:ICacheableArmature):void + { + cacheGeneratorArmature = armature; + + var slotDic:Object = armature.getSlotDic(); + var cacheUser:ICacheUser; +// for each(cacheUser in armature._boneDic) +// { +// boneFrameCacheDic[cacheUser.name] = new FrameCache(); +// } + for each(cacheUser in armature.getSlotDic()) + { + slotFrameCacheDic[cacheUser.name] = new SlotFrameCache(); + } + + for each(var animationCache:AnimationCache in animationCacheDic) + { +// animationCache.initBoneTimelineCacheDic(armature._boneDic, boneFrameCacheDic); + animationCache.initSlotTimelineCacheDic(slotDic, slotFrameCacheDic); + } + } + + public function generateAllAnimationCache(loop:Boolean):void + { + for each(var animationCache:AnimationCache in animationCacheDic) + { + generateAnimationCache(animationCache.name, loop); + } + } + + public function generateAnimationCache(animationName:String, loop:Boolean):void + { + var temp:Boolean = cacheGeneratorArmature.enableCache; + cacheGeneratorArmature.enableCache = false; + var animationCache:AnimationCache = animationCacheDic[animationName]; + if(!animationCache) + { + return; + } + + var animationState:IAnimationState = cacheGeneratorArmature.getAnimation().animationState; + var passTime:Number = 1 / frameRate; + + if (loop) + { + cacheGeneratorArmature.getAnimation().gotoAndPlay(animationName,0,-1,0); + } + else + { + cacheGeneratorArmature.getAnimation().gotoAndPlay(animationName,0,-1,1); + } + + var tempEnableEventDispatch:Boolean = cacheGeneratorArmature.enableEventDispatch; + cacheGeneratorArmature.enableEventDispatch = false; + var lastProgress:Number; + do + { + lastProgress = animationState.progress; + cacheGeneratorArmature.advanceTime(passTime); + animationCache.addFrame(); + } + while (animationState.progress >= lastProgress && animationState.progress < 1); + + cacheGeneratorArmature.enableEventDispatch = tempEnableEventDispatch; + resetCacheGeneratorArmature(); + cacheGeneratorArmature.enableCache = temp; + } + + /** + * 将缓存生成器骨架重置,生成动画缓存后调用。 + */ + public function resetCacheGeneratorArmature():void + { + cacheGeneratorArmature.resetAnimation(); + } + + public function getAnimationCache(animationName:String):AnimationCache + { + return animationCacheDic[animationName]; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/cache/FrameCache.as b/srclib/dragonBones/cache/FrameCache.as new file mode 100644 index 00000000..23d24859 --- /dev/null +++ b/srclib/dragonBones/cache/FrameCache.as @@ -0,0 +1,31 @@ +package dragonBones.cache { + + import dragonBones.objects.DBTransform; + + import flash.geom.Matrix; + + public class FrameCache + { + private static const ORIGIN_TRAMSFORM:DBTransform = new DBTransform(); + private static const ORIGIN_MATRIX:Matrix = new Matrix(); + + public var globalTransform:DBTransform = new DBTransform(); + public var globalTransformMatrix:Matrix = new Matrix(); + public function FrameCache() + { + } + + //浅拷贝提高效率 + public function copy(frameCache:FrameCache):void + { + globalTransform = frameCache.globalTransform; + globalTransformMatrix = frameCache.globalTransformMatrix; + } + + public function clear():void + { + globalTransform = ORIGIN_TRAMSFORM; + globalTransformMatrix = ORIGIN_MATRIX; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/cache/SlotFrameCache.as b/srclib/dragonBones/cache/SlotFrameCache.as new file mode 100644 index 00000000..a594bdfe --- /dev/null +++ b/srclib/dragonBones/cache/SlotFrameCache.as @@ -0,0 +1,30 @@ +package dragonBones.cache { + + import flash.geom.ColorTransform; + + public class SlotFrameCache extends FrameCache + { + public var colorTransform:ColorTransform; + public var displayIndex:int = -1; +// public var zOrder:int; + public function SlotFrameCache() + { + super(); + } + + //浅拷贝提高效率 + override public function copy(frameCache:FrameCache):void + { + super.copy(frameCache); + colorTransform = (frameCache as SlotFrameCache).colorTransform; + displayIndex = (frameCache as SlotFrameCache).displayIndex; + } + + override public function clear():void + { + super.clear(); + colorTransform = null; + displayIndex = -1; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/cache/SlotTimelineCache.as b/srclib/dragonBones/cache/SlotTimelineCache.as new file mode 100644 index 00000000..d3bb9287 --- /dev/null +++ b/srclib/dragonBones/cache/SlotTimelineCache.as @@ -0,0 +1,27 @@ +package dragonBones.cache { + + import dragonBones.core.ISlotCacheGenerator; + import dragonBones.utils.ColorTransformUtil; + + public class SlotTimelineCache extends TimelineCache + { + public var cacheGenerator:ISlotCacheGenerator; + public function SlotTimelineCache() + { + super(); + } + + override public function addFrame():void + { + var cache:SlotFrameCache = new SlotFrameCache(); + cache.globalTransform.copy(cacheGenerator.global); + cache.globalTransformMatrix.copyFrom(cacheGenerator.globalTransformMatrix); + if(cacheGenerator.colorChanged) + { + cache.colorTransform = ColorTransformUtil.cloneColor(cacheGenerator.colorTransform); + } + cache.displayIndex = cacheGenerator.displayIndex; + frameCacheList.push(cache); + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/cache/TimelineCache.as b/srclib/dragonBones/cache/TimelineCache.as new file mode 100644 index 00000000..bee2eec8 --- /dev/null +++ b/srclib/dragonBones/cache/TimelineCache.as @@ -0,0 +1,27 @@ +package dragonBones.cache { + + import dragonBones.core.ICacheUser; + + public class TimelineCache + { + public var name:String; + public var frameCacheList:Vector. = new Vector.(); + public var currentFrameCache:FrameCache; + public function TimelineCache() + { + } + + public function addFrame():void + { + } + public function update(frameIndex:int):void + { + currentFrameCache.copy(frameCacheList[frameIndex]); + } + + public function bindCacheUser(cacheUser:ICacheUser):void + { + cacheUser.frameCache = currentFrameCache; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/core/DBObject.as b/srclib/dragonBones/core/DBObject.as new file mode 100644 index 00000000..b9ace477 --- /dev/null +++ b/srclib/dragonBones/core/DBObject.as @@ -0,0 +1,226 @@ +package dragonBones.core { + + import dragonBones.Armature; + import dragonBones.Bone; + import dragonBones.objects.DBTransform; + import dragonBones.utils.TransformUtil; + + import flash.geom.Matrix; + + use namespace dragonBones_internal; + + public class DBObject + { + public var name:String; + + /** + * An object that can contain any user extra data. + */ + public var userData:Object; + + /** + * + */ + public var inheritRotation:Boolean; + + /** + * + */ + public var inheritScale:Boolean; + + /** + * + */ + public var inheritTranslation:Boolean; + + /** @private */ + dragonBones_internal var _global:DBTransform; + /** @private */ + dragonBones_internal var _globalTransformMatrix:Matrix; + + dragonBones_internal static var _tempParentGlobalTransformMatrix:Matrix = new Matrix(); + dragonBones_internal static var _tempParentGlobalTransform:DBTransform = new DBTransform(); + + + /** + * This DBObject instance global transform instance. + * @see dragonBones.objects.DBTransform + */ + public function get global():DBTransform + { + return _global; + } + + /** @private */ + protected var _origin:DBTransform; + /** + * This DBObject instance related to parent transform instance. + * @see dragonBones.objects.DBTransform + */ + public function get origin():DBTransform + { + return _origin; + } + + /** @private */ + protected var _offset:DBTransform; + /** + * This DBObject instance offset transform instance (For manually control). + * @see dragonBones.objects.DBTransform + */ + public function get offset():DBTransform + { + return _offset; + } + + /** @private */ + protected var _visible:Boolean; + public function get visible():Boolean + { + return _visible; + } + public function set visible(value:Boolean):void + { + _visible = value; + } + + /** @private */ + protected var _armature:Armature; + /** + * The armature this DBObject instance belongs to. + */ + public function get armature():Armature + { + return _armature; + } + /** @private */ + dragonBones_internal function setArmature(value:Armature):void + { + _armature = value; + } + + /** @private */ + dragonBones_internal var _parent:Bone; + /** + * Indicates the Bone instance that directly contains this DBObject instance if any. + */ + public function get parent():Bone + { + return _parent; + } + /** @private */ + dragonBones_internal function setParent(value:Bone):void + { + _parent = value; + } + + public function DBObject() + { + _globalTransformMatrix = new Matrix(); + + _global = new DBTransform(); + _origin = new DBTransform(); + _offset = new DBTransform(); + _offset.scaleX = _offset.scaleY = 1; + + _visible = true; + + _armature = null; + _parent = null; + + userData = null; + + this.inheritRotation = true; + this.inheritScale = true; + this.inheritTranslation = true; + } + + /** + * Cleans up any resources used by this DBObject instance. + */ + public function dispose():void + { + userData = null; + + _globalTransformMatrix = null; + _global = null; + _origin = null; + _offset = null; + + _armature = null; + _parent = null; + } + + protected function calculateRelativeParentTransform():void + { + } + + protected function calculateParentTransform():Object + { + if(this.parent && (this.inheritTranslation || this.inheritRotation || this.inheritScale)) + { + var parentGlobalTransform:DBTransform = this._parent._globalTransformForChild; + var parentGlobalTransformMatrix:Matrix = this._parent._globalTransformMatrixForChild; + + if(!this.inheritTranslation || !this.inheritRotation || !this.inheritScale) + { + parentGlobalTransform = DBObject._tempParentGlobalTransform; + parentGlobalTransform.copy(this._parent._globalTransformForChild); + if(!this.inheritTranslation) + { + parentGlobalTransform.x = 0; + parentGlobalTransform.y = 0; + } + if(!this.inheritScale) + { + parentGlobalTransform.scaleX = 1; + parentGlobalTransform.scaleY = 1; + } + if(!this.inheritRotation) + { + parentGlobalTransform.skewX = 0; + parentGlobalTransform.skewY = 0; + } + + parentGlobalTransformMatrix = DBObject._tempParentGlobalTransformMatrix; + TransformUtil.transformToMatrix(parentGlobalTransform, parentGlobalTransformMatrix); + } + + return {parentGlobalTransform:parentGlobalTransform, parentGlobalTransformMatrix:parentGlobalTransformMatrix}; + } + return null; + } + + protected function updateGlobal():Object + { + calculateRelativeParentTransform(); + var output:Object = calculateParentTransform(); + if(output != null) + { + //计算父骨头绝对坐标 + var parentMatrix:Matrix = output.parentGlobalTransformMatrix; + var parentGlobalTransform:DBTransform = output.parentGlobalTransform; + //计算绝对坐标 + var x:Number = _global.x; + var y:Number = _global.y; + + _global.x = parentMatrix.a * x + parentMatrix.c * y + parentMatrix.tx; + _global.y = parentMatrix.d * y + parentMatrix.b * x + parentMatrix.ty; + + if(this.inheritRotation) + { + _global.skewX += parentGlobalTransform.skewX; + _global.skewY += parentGlobalTransform.skewY; + } + + if(this.inheritScale) + { + _global.scaleX *= parentGlobalTransform.scaleX; + _global.scaleY *= parentGlobalTransform.scaleY; + } + } + TransformUtil.transformToMatrix(_global, _globalTransformMatrix); + return output; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/core/DragonBones.as b/srclib/dragonBones/core/DragonBones.as new file mode 100644 index 00000000..9a11f745 --- /dev/null +++ b/srclib/dragonBones/core/DragonBones.as @@ -0,0 +1,14 @@ +package dragonBones.core +{ + public final class DragonBones + { + public static const DATA_VERSION:String = "4.0"; + public static const PARENT_COORDINATE_DATA_VERSION:String = "3.0"; + + public static const VERSION:String = "4.1"; + + public function DragonBones() + { + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/core/IAnimationState.as b/srclib/dragonBones/core/IAnimationState.as new file mode 100644 index 00000000..89797176 --- /dev/null +++ b/srclib/dragonBones/core/IAnimationState.as @@ -0,0 +1,7 @@ +package dragonBones.core +{ + public interface IAnimationState + { + function get progress():Number + } +} \ No newline at end of file diff --git a/srclib/dragonBones/core/IArmature.as b/srclib/dragonBones/core/IArmature.as new file mode 100644 index 00000000..ed305d47 --- /dev/null +++ b/srclib/dragonBones/core/IArmature.as @@ -0,0 +1,11 @@ +package dragonBones.core { + + import dragonBones.animation.IAnimatable; + + public interface IArmature extends IAnimatable + { + function getAnimation():Object; + function resetAnimation():void + + } +} \ No newline at end of file diff --git a/srclib/dragonBones/core/ICacheUser.as b/srclib/dragonBones/core/ICacheUser.as new file mode 100644 index 00000000..c4852f8e --- /dev/null +++ b/srclib/dragonBones/core/ICacheUser.as @@ -0,0 +1,11 @@ +package dragonBones.core { + + import dragonBones.cache.FrameCache; + + public interface ICacheUser + { + function get name():String; + function set frameCache(cache:FrameCache):void; + + } +} \ No newline at end of file diff --git a/srclib/dragonBones/core/ICacheableArmature.as b/srclib/dragonBones/core/ICacheableArmature.as new file mode 100644 index 00000000..3e50c7f5 --- /dev/null +++ b/srclib/dragonBones/core/ICacheableArmature.as @@ -0,0 +1,13 @@ +package dragonBones.core +{ + public interface ICacheableArmature extends IArmature + { + function get enableCache():Boolean; + function set enableCache(value:Boolean):void; + + function get enableEventDispatch():Boolean; + function set enableEventDispatch(value:Boolean):void; + + function getSlotDic():Object; + } +} \ No newline at end of file diff --git a/srclib/dragonBones/core/ISlotCacheGenerator.as b/srclib/dragonBones/core/ISlotCacheGenerator.as new file mode 100644 index 00000000..82938263 --- /dev/null +++ b/srclib/dragonBones/core/ISlotCacheGenerator.as @@ -0,0 +1,16 @@ +package dragonBones.core { + + import dragonBones.objects.DBTransform; + + import flash.geom.ColorTransform; + import flash.geom.Matrix; + + public interface ISlotCacheGenerator extends ICacheUser + { + function get global():DBTransform; + function get globalTransformMatrix():Matrix; + function get colorChanged():Boolean; + function get colorTransform():ColorTransform; + function get displayIndex():int; + } +} \ No newline at end of file diff --git a/srclib/dragonBones/core/dragonBones_internal.as b/srclib/dragonBones/core/dragonBones_internal.as new file mode 100644 index 00000000..44d9c341 --- /dev/null +++ b/srclib/dragonBones/core/dragonBones_internal.as @@ -0,0 +1,6 @@ +package dragonBones.core +{ + + /** @private */ + public namespace dragonBones_internal; +} \ No newline at end of file diff --git a/srclib/dragonBones/display/NativeFastSlot.as b/srclib/dragonBones/display/NativeFastSlot.as new file mode 100644 index 00000000..1ca1d587 --- /dev/null +++ b/srclib/dragonBones/display/NativeFastSlot.as @@ -0,0 +1,146 @@ +package dragonBones.display { + + import dragonBones.core.dragonBones_internal; + import dragonBones.fast.FastSlot; + + import flash.display.BlendMode; + import flash.display.DisplayObject; + import flash.display.DisplayObjectContainer; + + use namespace dragonBones_internal; + + public class NativeFastSlot extends FastSlot + { + private var _nativeDisplay:DisplayObject; + + public function NativeFastSlot() + { + super(this); + _nativeDisplay = null; + } + + override public function dispose():void + { + super.dispose(); + + _nativeDisplay = null; + } + + + //Abstract method + + /** @private */ + override dragonBones_internal function updateDisplay(value:Object):void + { + _nativeDisplay = value as DisplayObject; + } + + /** @private */ + override dragonBones_internal function getDisplayIndex():int + { + if(_nativeDisplay && _nativeDisplay.parent) + { + return _nativeDisplay.parent.getChildIndex(_nativeDisplay); + } + return -1; + } + + /** @private */ + override dragonBones_internal function addDisplayToContainer(container:Object, index:int = -1):void + { + var nativeContainer:DisplayObjectContainer = container as DisplayObjectContainer; + if(_nativeDisplay && nativeContainer) + { + if (index < 0) + { + nativeContainer.addChild(_nativeDisplay); + + } + else + { + nativeContainer.addChildAt(_nativeDisplay, Math.min(index, nativeContainer.numChildren)); + } + } + } + + /** @private */ + override dragonBones_internal function removeDisplayFromContainer():void + { + if(_nativeDisplay && _nativeDisplay.parent) + { + _nativeDisplay.parent.removeChild(_nativeDisplay); + } + } + + /** @private */ + override dragonBones_internal function updateTransform():void + { + if(_nativeDisplay) + { + _nativeDisplay.transform.matrix = this._globalTransformMatrix; + } + } + + /** @private */ + override dragonBones_internal function updateDisplayVisible(value:Boolean):void + { + //if(_nativeDisplay) + //{ + //_nativeDisplay.visible = this._parent.visible && this._visible && value; + //} + } + + /** @private */ + override dragonBones_internal function updateDisplayColor( + aOffset:Number, + rOffset:Number, + gOffset:Number, + bOffset:Number, + aMultiplier:Number, + rMultiplier:Number, + gMultiplier:Number, + bMultiplier:Number, + colorChanged:Boolean = false):void + { + if(_nativeDisplay) + { + super.updateDisplayColor(aOffset, rOffset, gOffset, bOffset, aMultiplier, rMultiplier, gMultiplier, bMultiplier,colorChanged); + + + _nativeDisplay.transform.colorTransform = _colorTransform; + } + } + + /** @private */ + override dragonBones_internal function updateDisplayBlendMode(value:String):void + { + if(_nativeDisplay) + { + switch(blendMode) + { + case BlendMode.ADD: + case BlendMode.ALPHA: + case BlendMode.DARKEN: + case BlendMode.DIFFERENCE: + case BlendMode.ERASE: + case BlendMode.HARDLIGHT: + case BlendMode.INVERT: + case BlendMode.LAYER: + case BlendMode.LIGHTEN: + case BlendMode.MULTIPLY: + case BlendMode.NORMAL: + case BlendMode.OVERLAY: + case BlendMode.SCREEN: + case BlendMode.SHADER: + case BlendMode.SUBTRACT: + _nativeDisplay.blendMode = blendMode; + break; + + default: + //_nativeDisplay.blendMode = BlendMode.NORMAL; + break; + } + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/display/NativeSlot.as b/srclib/dragonBones/display/NativeSlot.as new file mode 100644 index 00000000..1873e140 --- /dev/null +++ b/srclib/dragonBones/display/NativeSlot.as @@ -0,0 +1,145 @@ +package dragonBones.display { + + import dragonBones.Slot; + import dragonBones.core.dragonBones_internal; + + import flash.display.BlendMode; + import flash.display.DisplayObject; + import flash.display.DisplayObjectContainer; + + use namespace dragonBones_internal; + + public class NativeSlot extends Slot + { + private var _nativeDisplay:DisplayObject; + + public function NativeSlot() + { + super(this); + _nativeDisplay = null; + } + + override public function dispose():void + { + super.dispose(); + + _nativeDisplay = null; + } + + + //Abstract method + + /** @private */ + override dragonBones_internal function updateDisplay(value:Object):void + { + _nativeDisplay = value as DisplayObject; + } + + /** @private */ + override dragonBones_internal function getDisplayIndex():int + { + if(_nativeDisplay && _nativeDisplay.parent) + { + return _nativeDisplay.parent.getChildIndex(_nativeDisplay); + } + return -1; + } + + /** @private */ + override dragonBones_internal function addDisplayToContainer(container:Object, index:int = -1):void + { + var nativeContainer:DisplayObjectContainer = container as DisplayObjectContainer; + if(_nativeDisplay && nativeContainer) + { + if (index < 0) + { + nativeContainer.addChild(_nativeDisplay); + } + else + { + nativeContainer.addChildAt(_nativeDisplay, Math.min(index, nativeContainer.numChildren)); + } + } + } + + /** @private */ + override dragonBones_internal function removeDisplayFromContainer():void + { + if(_nativeDisplay && _nativeDisplay.parent) + { + _nativeDisplay.parent.removeChild(_nativeDisplay); + } + } + + /** @private */ + override dragonBones_internal function updateTransform():void + { + if(_nativeDisplay) + { + _nativeDisplay.transform.matrix = this._globalTransformMatrix; + } + } + + /** @private */ + override dragonBones_internal function updateDisplayVisible(value:Boolean):void + { + if(_nativeDisplay) + { + _nativeDisplay.visible = this._parent.visible && this._visible && value; + } + } + + /** @private */ + override dragonBones_internal function updateDisplayColor( + aOffset:Number, + rOffset:Number, + gOffset:Number, + bOffset:Number, + aMultiplier:Number, + rMultiplier:Number, + gMultiplier:Number, + bMultiplier:Number, + colorChanged:Boolean = false):void + { + if(_nativeDisplay) + { + super.updateDisplayColor(aOffset, rOffset, gOffset, bOffset, aMultiplier, rMultiplier, gMultiplier, bMultiplier,colorChanged); + + + _nativeDisplay.transform.colorTransform = _colorTransform; + } + } + + /** @private */ + override dragonBones_internal function updateDisplayBlendMode(value:String):void + { + if(_nativeDisplay) + { + switch(blendMode) + { + case BlendMode.ADD: + case BlendMode.ALPHA: + case BlendMode.DARKEN: + case BlendMode.DIFFERENCE: + case BlendMode.ERASE: + case BlendMode.HARDLIGHT: + case BlendMode.INVERT: + case BlendMode.LAYER: + case BlendMode.LIGHTEN: + case BlendMode.MULTIPLY: + case BlendMode.NORMAL: + case BlendMode.OVERLAY: + case BlendMode.SCREEN: + case BlendMode.SHADER: + case BlendMode.SUBTRACT: + _nativeDisplay.blendMode = blendMode; + break; + + default: + //_nativeDisplay.blendMode = BlendMode.NORMAL; + break; + } + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/display/StarlingFastSlot.as b/srclib/dragonBones/display/StarlingFastSlot.as new file mode 100644 index 00000000..11e54e40 --- /dev/null +++ b/srclib/dragonBones/display/StarlingFastSlot.as @@ -0,0 +1,224 @@ +package dragonBones.display { + + import dragonBones.core.dragonBones_internal; + import dragonBones.fast.FastArmature; + import dragonBones.fast.FastSlot; + + import starling.display.BlendMode; + import starling.display.DisplayObject; + import starling.display.DisplayObjectContainer; + import starling.display.Quad; + + import flash.display.BlendMode; + import flash.geom.Matrix; + + use namespace dragonBones_internal; + + public class StarlingFastSlot extends FastSlot + { + private var _starlingDisplay:DisplayObject; + + public var updateMatrix:Boolean; + + + public function StarlingFastSlot() + { + super(this); + + _starlingDisplay = null; + + updateMatrix = false; + } + + override public function dispose():void + { + for each(var content:Object in this._displayList) + { + if(content is FastArmature) + { + (content as FastArmature).dispose(); + } + else if(content is DisplayObject) + { + (content as DisplayObject).dispose(); + } + } + super.dispose(); + + _starlingDisplay = null; + } + + /** @private */ + override dragonBones_internal function updateDisplay(value:Object):void + { + _starlingDisplay = value as DisplayObject; + } + + + //Abstract method + + /** @private */ + override dragonBones_internal function getDisplayIndex():int + { + if(_starlingDisplay && _starlingDisplay.parent) + { + return _starlingDisplay.parent.getChildIndex(_starlingDisplay); + } + return -1; + } + + /** @private */ + override dragonBones_internal function addDisplayToContainer(container:Object, index:int = -1):void + { + var starlingContainer:DisplayObjectContainer = container as DisplayObjectContainer; + if(_starlingDisplay && starlingContainer) + { + if (index < 0) + { + starlingContainer.addChild(_starlingDisplay); + } + else + { + starlingContainer.addChildAt(_starlingDisplay, Math.min(index, starlingContainer.numChildren)); + } + } + } + + /** @private */ + override dragonBones_internal function removeDisplayFromContainer():void + { + if(_starlingDisplay && _starlingDisplay.parent) + { + _starlingDisplay.parent.removeChild(_starlingDisplay); + } + } + + /** @private */ + override dragonBones_internal function updateTransform():void + { + if(_starlingDisplay) + { + var pivotX:Number = _starlingDisplay.pivotX; + var pivotY:Number = _starlingDisplay.pivotY; + + + if(updateMatrix) + { + //_starlingDisplay.transformationMatrix setter 比较慢暂时走下面 + _starlingDisplay.transformationMatrix = _globalTransformMatrix; + if(pivotX || pivotY) + { + _starlingDisplay.pivotX = pivotX; + _starlingDisplay.pivotY = pivotY; + } + } + else + { + var displayMatrix:Matrix = _starlingDisplay.transformationMatrix; + displayMatrix.a = _globalTransformMatrix.a; + displayMatrix.b = _globalTransformMatrix.b; + displayMatrix.c = _globalTransformMatrix.c; + displayMatrix.d = _globalTransformMatrix.d; + //displayMatrix.copyFrom(_globalTransformMatrix); + if(pivotX || pivotY) + { + displayMatrix.tx = _globalTransformMatrix.tx - (displayMatrix.a * pivotX + displayMatrix.c * pivotY); + displayMatrix.ty = _globalTransformMatrix.ty - (displayMatrix.b * pivotX + displayMatrix.d * pivotY); + } + else + { + displayMatrix.tx = _globalTransformMatrix.tx; + displayMatrix.ty = _globalTransformMatrix.ty; + } + } + } + } + + /** @private */ + override dragonBones_internal function updateDisplayVisible(value:Boolean):void + { +// if(_starlingDisplay && this._parent) +// { +// _starlingDisplay.visible = this._parent.visible && this._visible && value; +// } + } + + /** @private */ + override dragonBones_internal function updateDisplayColor( + aOffset:Number, + rOffset:Number, + gOffset:Number, + bOffset:Number, + aMultiplier:Number, + rMultiplier:Number, + gMultiplier:Number, + bMultiplier:Number, + colorChanged:Boolean = false):void + { + if(_starlingDisplay) + { + super.updateDisplayColor(aOffset, rOffset, gOffset, bOffset, aMultiplier, rMultiplier, gMultiplier, bMultiplier,colorChanged); + _starlingDisplay.alpha = aMultiplier; + if (_starlingDisplay is Quad) + { + (_starlingDisplay as Quad).color = (uint(rMultiplier * 0xff) << 16) + (uint(gMultiplier * 0xff) << 8) + uint(bMultiplier * 0xff); + } + } + } + + /** @private */ + override dragonBones_internal function updateDisplayBlendMode(value:String):void + { + if(_starlingDisplay) + { + switch(blendMode) + { + case starling.display.BlendMode.NONE: + case starling.display.BlendMode.AUTO: + case starling.display.BlendMode.ADD: + case starling.display.BlendMode.ERASE: + case starling.display.BlendMode.MULTIPLY: + case starling.display.BlendMode.NORMAL: + case starling.display.BlendMode.SCREEN: + _starlingDisplay.blendMode = blendMode; + break; + + case flash.display.BlendMode.ADD: + _starlingDisplay.blendMode = starling.display.BlendMode.ADD; + break; + + case flash.display.BlendMode.ERASE: + _starlingDisplay.blendMode = starling.display.BlendMode.ERASE; + break; + + case flash.display.BlendMode.MULTIPLY: + _starlingDisplay.blendMode = starling.display.BlendMode.MULTIPLY; + break; + + case flash.display.BlendMode.NORMAL: + _starlingDisplay.blendMode = starling.display.BlendMode.NORMAL; + break; + + case flash.display.BlendMode.SCREEN: + _starlingDisplay.blendMode = starling.display.BlendMode.SCREEN; + break; + + case flash.display.BlendMode.ALPHA: + case flash.display.BlendMode.DARKEN: + case flash.display.BlendMode.DIFFERENCE: + case flash.display.BlendMode.HARDLIGHT: + case flash.display.BlendMode.INVERT: + case flash.display.BlendMode.LAYER: + case flash.display.BlendMode.LIGHTEN: + case flash.display.BlendMode.OVERLAY: + case flash.display.BlendMode.SHADER: + case flash.display.BlendMode.SUBTRACT: + break; + + default: + break; + } + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/display/StarlingSlot.as b/srclib/dragonBones/display/StarlingSlot.as new file mode 100644 index 00000000..2c2a7be7 --- /dev/null +++ b/srclib/dragonBones/display/StarlingSlot.as @@ -0,0 +1,223 @@ +package dragonBones.display { + + import dragonBones.Armature; + import dragonBones.Slot; + import dragonBones.core.dragonBones_internal; + + import starling.display.BlendMode; + import starling.display.DisplayObject; + import starling.display.DisplayObjectContainer; + import starling.display.Quad; + + import flash.display.BlendMode; + import flash.geom.Matrix; + + use namespace dragonBones_internal; + + public class StarlingSlot extends Slot + { + private var _starlingDisplay:DisplayObject; + + public var updateMatrix:Boolean; + + public function StarlingSlot() + { + super(this); + + _starlingDisplay = null; + + updateMatrix = false; + } + + override public function dispose():void + { + for each(var content:Object in this._displayList) + { + if(content is Armature) + { + (content as Armature).dispose(); + } + else if(content is DisplayObject) + { + (content as DisplayObject).dispose(); + } + } + super.dispose(); + + _starlingDisplay = null; + } + + /** @private */ + override dragonBones_internal function updateDisplay(value:Object):void + { + _starlingDisplay = value as DisplayObject; + } + + + //Abstract method + + /** @private */ + override dragonBones_internal function getDisplayIndex():int + { + if(_starlingDisplay && _starlingDisplay.parent) + { + return _starlingDisplay.parent.getChildIndex(_starlingDisplay); + } + return -1; + } + + /** @private */ + override dragonBones_internal function addDisplayToContainer(container:Object, index:int = -1):void + { + var starlingContainer:DisplayObjectContainer = container as DisplayObjectContainer; + if(_starlingDisplay && starlingContainer) + { + if (index < 0) + { + starlingContainer.addChild(_starlingDisplay); + } + else + { + starlingContainer.addChildAt(_starlingDisplay, Math.min(index, starlingContainer.numChildren)); + } + } + } + + /** @private */ + override dragonBones_internal function removeDisplayFromContainer():void + { + if(_starlingDisplay && _starlingDisplay.parent) + { + _starlingDisplay.parent.removeChild(_starlingDisplay); + } + } + + /** @private */ + override dragonBones_internal function updateTransform():void + { + if(_starlingDisplay) + { + var pivotX:Number = _starlingDisplay.pivotX; + var pivotY:Number = _starlingDisplay.pivotY; + + + if(updateMatrix) + { + //_starlingDisplay.transformationMatrix setter 比较慢暂时走下面 + _starlingDisplay.transformationMatrix = _globalTransformMatrix; + if(pivotX || pivotY) + { + _starlingDisplay.pivotX = pivotX; + _starlingDisplay.pivotY = pivotY; + } + } + else + { + var displayMatrix:Matrix = _starlingDisplay.transformationMatrix; + displayMatrix.a = _globalTransformMatrix.a; + displayMatrix.b = _globalTransformMatrix.b; + displayMatrix.c = _globalTransformMatrix.c; + displayMatrix.d = _globalTransformMatrix.d; + //displayMatrix.copyFrom(_globalTransformMatrix); + if(pivotX || pivotY) + { + displayMatrix.tx = _globalTransformMatrix.tx - (displayMatrix.a * pivotX + displayMatrix.c * pivotY); + displayMatrix.ty = _globalTransformMatrix.ty - (displayMatrix.b * pivotX + displayMatrix.d * pivotY); + } + else + { + displayMatrix.tx = _globalTransformMatrix.tx; + displayMatrix.ty = _globalTransformMatrix.ty; + } + } + } + } + + /** @private */ + override dragonBones_internal function updateDisplayVisible(value:Boolean):void + { + if(_starlingDisplay && this._parent) + { + _starlingDisplay.visible = this._parent.visible && this._visible && value; + } + } + + /** @private */ + override dragonBones_internal function updateDisplayColor( + aOffset:Number, + rOffset:Number, + gOffset:Number, + bOffset:Number, + aMultiplier:Number, + rMultiplier:Number, + gMultiplier:Number, + bMultiplier:Number, + colorChanged:Boolean = false):void + { + if(_starlingDisplay) + { + super.updateDisplayColor(aOffset, rOffset, gOffset, bOffset, aMultiplier, rMultiplier, gMultiplier, bMultiplier,colorChanged); + _starlingDisplay.alpha = aMultiplier; + if (_starlingDisplay is Quad) + { + (_starlingDisplay as Quad).color = (uint(rMultiplier * 0xff) << 16) + (uint(gMultiplier * 0xff) << 8) + uint(bMultiplier * 0xff); + } + } + } + + /** @private */ + override dragonBones_internal function updateDisplayBlendMode(value:String):void + { + if(_starlingDisplay) + { + switch(blendMode) + { + case starling.display.BlendMode.NONE: + case starling.display.BlendMode.AUTO: + case starling.display.BlendMode.ADD: + case starling.display.BlendMode.ERASE: + case starling.display.BlendMode.MULTIPLY: + case starling.display.BlendMode.NORMAL: + case starling.display.BlendMode.SCREEN: + _starlingDisplay.blendMode = blendMode; + break; + + case flash.display.BlendMode.ADD: + _starlingDisplay.blendMode = starling.display.BlendMode.ADD; + break; + + case flash.display.BlendMode.ERASE: + _starlingDisplay.blendMode = starling.display.BlendMode.ERASE; + break; + + case flash.display.BlendMode.MULTIPLY: + _starlingDisplay.blendMode = starling.display.BlendMode.MULTIPLY; + break; + + case flash.display.BlendMode.NORMAL: + _starlingDisplay.blendMode = starling.display.BlendMode.NORMAL; + break; + + case flash.display.BlendMode.SCREEN: + _starlingDisplay.blendMode = starling.display.BlendMode.SCREEN; + break; + + case flash.display.BlendMode.ALPHA: + case flash.display.BlendMode.DARKEN: + case flash.display.BlendMode.DIFFERENCE: + case flash.display.BlendMode.HARDLIGHT: + case flash.display.BlendMode.INVERT: + case flash.display.BlendMode.LAYER: + case flash.display.BlendMode.LIGHTEN: + case flash.display.BlendMode.OVERLAY: + case flash.display.BlendMode.SHADER: + case flash.display.BlendMode.SUBTRACT: + break; + + default: + break; + } + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/events/AnimationEvent.as b/srclib/dragonBones/events/AnimationEvent.as new file mode 100644 index 00000000..9a055c57 --- /dev/null +++ b/srclib/dragonBones/events/AnimationEvent.as @@ -0,0 +1,111 @@ +package dragonBones.events { + + import dragonBones.Armature; + + import flash.events.Event; + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0, Flash 10 + * @langversion 3.0 + * @version 2.0 + */ + + /** + * The AnimationEvent provides and defines all events dispatched during an animation. + * + * @see dragonBones.Armature + * @see dragonBones.animation.Animation + */ + public class AnimationEvent extends Event + { + /** + * 不推荐使用. + */ + public static function get MOVEMENT_CHANGE():String + { + return FADE_IN; + } + + /** + * Dispatched when the playback of an animation fade in. + */ + public static const FADE_IN:String = "fadeIn"; + + /** + * Dispatched when the playback of an animation fade out. + */ + public static const FADE_OUT:String = "fadeOut"; + + /** + * Dispatched when the playback of an animation starts. + */ + public static const START:String = "start"; + + /** + * Dispatched when the playback of a animation stops. + */ + public static const COMPLETE:String = "complete"; + + /** + * Dispatched when the playback of a animation completes a loop. + */ + public static const LOOP_COMPLETE:String = "loopComplete"; + + /** + * Dispatched when the playback of an animation fade in complete. + */ + public static const FADE_IN_COMPLETE:String = "fadeInComplete"; + + /** + * Dispatched when the playback of an animation fade out complete. + */ + public static const FADE_OUT_COMPLETE:String = "fadeOutComplete"; + + /** + * 不推荐的API. + */ + public function get movementID():String + { + return animationName; + } + + /** + * The animationState instance. + */ + public var animationState:Object; + + /** + * The armature that is the taget of this event. + */ + public function get armature():Armature + { + return target as Armature; + } + + public function get animationName():String + { + return animationState.name; + } + + /** + * Creates a new AnimationEvent instance. + * @param type + * @param cancelable + */ + public function AnimationEvent(type:String, cancelable:Boolean = false) + { + super(type, false, cancelable); + } + + /** + * @private + * @return + */ + override public function clone():Event + { + var event:AnimationEvent = new AnimationEvent(type, cancelable); + event.animationState = animationState; + return event; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/events/ArmatureEvent.as b/srclib/dragonBones/events/ArmatureEvent.as new file mode 100644 index 00000000..c43c5199 --- /dev/null +++ b/srclib/dragonBones/events/ArmatureEvent.as @@ -0,0 +1,38 @@ +package dragonBones.events { + + import flash.events.Event; + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0, Flash 10 + * @langversion 3.0 + * @version 2.0 + */ + /** + * The ArmatureEvent provides and defines all events dispatched directly by an Armature instance. + * + * + * @see dragonBones.animation.Animation + */ + public class ArmatureEvent extends Event + { + + /** + * Dispatched after a successful z order update. + */ + public static const Z_ORDER_UPDATED:String = "zOrderUpdated"; + + public function ArmatureEvent(type:String) + { + super(type, false, false); + } + + /** + * @private + * @return + */ + override public function clone():Event + { + return new ArmatureEvent(type); + } + } +} diff --git a/srclib/dragonBones/events/FrameEvent.as b/srclib/dragonBones/events/FrameEvent.as new file mode 100644 index 00000000..8a8f9dfe --- /dev/null +++ b/srclib/dragonBones/events/FrameEvent.as @@ -0,0 +1,81 @@ +package dragonBones.events { + + import dragonBones.Armature; + + import flash.events.Event; + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0, Flash 10 + * @langversion 3.0 + * @version 2.0 + */ + + /** + * The FrameEvent class provides and defines all events dispatched by an Animation or Bone instance entering a new frame. + * + * + * @see dragonBones.animation.Animation + */ + public class FrameEvent extends Event + { + public static function get MOVEMENT_FRAME_EVENT():String + { + return ANIMATION_FRAME_EVENT; + } + + /** + * Dispatched when the animation of the armatrue enter a frame. + */ + public static const ANIMATION_FRAME_EVENT:String = "animationFrameEvent"; + + /** + * + */ + public static const BONE_FRAME_EVENT:String ="boneFrameEvent"; + + /** + * The entered frame label. + */ + public var frameLabel:String; + + public var bone:Object; + + /** + * The armature that is the target of this event. + */ + public function get armature():Armature + { + return target as Armature; + } + + /** + * The animationState instance. + */ + public var animationState:Object; + + /** + * Creates a new FrameEvent instance. + * @param type + * @param cancelable + */ + public function FrameEvent(type:String, cancelable:Boolean = false) + { + super(type, false, cancelable); + } + + /** + * @private + * + * @return An exact duplicate of the current object. + */ + override public function clone():Event + { + var event:FrameEvent = new FrameEvent(type, cancelable); + event.animationState = animationState; + event.bone = bone; + event.animationState = animationState; + event.frameLabel = frameLabel; + return event; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/events/SoundEvent.as b/srclib/dragonBones/events/SoundEvent.as new file mode 100644 index 00000000..5307ce04 --- /dev/null +++ b/srclib/dragonBones/events/SoundEvent.as @@ -0,0 +1,58 @@ +package dragonBones.events { + + import dragonBones.Armature; + import dragonBones.animation.AnimationState; + + import flash.events.Event; + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0 + * @langversion 3.0 + * @version 2.0 + */ + + /** + * The SoundEvent provides and defines all sound related events dispatched during an animation. + * + * @see dragonBones.Armature + * @see dragonBones.animation.Animation + */ + public class SoundEvent extends Event + { + /** + * Dispatched when the animation of the animation enter a frame containing sound labels. + */ + public static const SOUND:String = "sound"; + + /** + * The armature that is the target of this event. + */ + public var armature:Armature; + + public var animationState:AnimationState; + + public var sound:String; + + /** + * Creates a new SoundEvent instance. + * @param type + * @param cancelable + */ + public function SoundEvent(type:String, cancelable:Boolean = false) + { + super(type, false, cancelable); + } + + /** + * @private + */ + override public function clone():Event + { + var event:SoundEvent = new SoundEvent(type, cancelable); + event.armature = armature; + event.animationState = animationState; + event.sound = sound; + return event; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/events/SoundEventManager.as b/srclib/dragonBones/events/SoundEventManager.as new file mode 100644 index 00000000..fcf08ab7 --- /dev/null +++ b/srclib/dragonBones/events/SoundEventManager.as @@ -0,0 +1,33 @@ +package dragonBones.events { + + import flash.errors.IllegalOperationError; + import flash.events.EventDispatcher; + + [Event(name="sound",type="dragonBones.events.SoundEvent")] + + /** + * 全局声音管理,通过监听SoundEventManager的SoundEvent事件得到动画的声音触发时间和声音的名字 + */ + public final class SoundEventManager extends EventDispatcher + { + private static var _instance:SoundEventManager; + + public static function getInstance():SoundEventManager + { + if (!_instance) + { + _instance = new SoundEventManager(); + } + return _instance; + } + + public function SoundEventManager() + { + super(); + if (_instance) + { + throw new IllegalOperationError("Singleton already constructed!"); + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/factories/BaseFactory.as b/srclib/dragonBones/factories/BaseFactory.as new file mode 100644 index 00000000..417a477d --- /dev/null +++ b/srclib/dragonBones/factories/BaseFactory.as @@ -0,0 +1,774 @@ +package dragonBones.factories { + + import dragonBones.Armature; + import dragonBones.Bone; + import dragonBones.Slot; + import dragonBones.core.dragonBones_internal; + import dragonBones.fast.FastArmature; + import dragonBones.fast.FastBone; + import dragonBones.fast.FastSlot; + import dragonBones.objects.ArmatureData; + import dragonBones.objects.BoneData; + import dragonBones.objects.DataParser; + import dragonBones.objects.DataSerializer; + import dragonBones.objects.DecompressedData; + import dragonBones.objects.DisplayData; + import dragonBones.objects.DragonBonesData; + import dragonBones.objects.SkinData; + import dragonBones.objects.SlotData; + import dragonBones.textures.ITextureAtlas; + + import flash.errors.IllegalOperationError; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.geom.Matrix; + import flash.utils.ByteArray; + import flash.utils.Dictionary; + + use namespace dragonBones_internal; + + public class BaseFactory extends EventDispatcher + { + protected static const _helpMatrix:Matrix = new Matrix(); + + /** @private */ + protected var dragonBonesDataDic:Dictionary = new Dictionary(); + + /** @private */ + protected var textureAtlasDic:Dictionary = new Dictionary(); + public function BaseFactory(self:BaseFactory) + { + super(this); + + if(self != this) + { + throw new IllegalOperationError("Abstract class can not be instantiated!"); + } + } + + /** + * Cleans up resources used by this BaseFactory instance. + * @param (optional) Destroy all internal references. + */ + public function dispose(disposeData:Boolean = true):void + { + if(disposeData) + { + for(var skeletonName:String in dragonBonesDataDic) + { + (dragonBonesDataDic[skeletonName] as DragonBonesData).dispose(); + delete dragonBonesDataDic[skeletonName]; + } + + for(var textureAtlasName:String in textureAtlasDic) + { + (textureAtlasDic[textureAtlasName] as ITextureAtlas).dispose(); + delete textureAtlasDic[textureAtlasName]; + } + } + + dragonBonesDataDic = null; + textureAtlasDic = null; + //_currentDataName = null; + //_currentTextureAtlasName = null; + } + + /** + * Returns a SkeletonData instance. + * @param The name of an existing SkeletonData instance. + * @return A SkeletonData instance with given name (if exist). + */ + public function getSkeletonData(name:String):DragonBonesData + { + return dragonBonesDataDic[name]; + } + + /** + * Add a SkeletonData instance to this BaseFactory instance. + * @param A SkeletonData instance. + * @param (optional) A name for this SkeletonData instance. + */ + public function addSkeletonData(data:DragonBonesData, name:String = null):void + { + if(!data) + { + throw new ArgumentError(); + } + name = name || data.name; + if(!name) + { + throw new ArgumentError("Unnamed data!"); + } + if(dragonBonesDataDic[name]) + { + throw new ArgumentError(); + } + dragonBonesDataDic[name] = data; + } + + /** + * Remove a SkeletonData instance from this BaseFactory instance. + * @param The name for the SkeletonData instance to remove. + */ + public function removeSkeletonData(name:String):void + { + delete dragonBonesDataDic[name]; + } + + /** + * Return the TextureAtlas by name. + * @param The name of the TextureAtlas to return. + * @return A textureAtlas. + */ + public function getTextureAtlas(name:String):Object + { + return textureAtlasDic[name]; + } + + /** + * Add a textureAtlas to this BaseFactory instance. + * @param A textureAtlas to add to this BaseFactory instance. + * @param (optional) A name for this TextureAtlas. + */ + public function addTextureAtlas(textureAtlas:Object, name:String = null):void + { + if(!textureAtlas) + { + throw new ArgumentError(); + } + if(!name && textureAtlas is ITextureAtlas) + { + name = textureAtlas.name; + } + if(!name) + { + throw new ArgumentError("Unnamed data!"); + } + if(textureAtlasDic[name]) + { + throw new ArgumentError(); + } + textureAtlasDic[name] = textureAtlas; + } + + /** + * Remove a textureAtlas from this baseFactory instance. + * @param The name of the TextureAtlas to remove. + */ + public function removeTextureAtlas(name:String):void + { + delete textureAtlasDic[name]; + } + + /** + * Return the TextureDisplay. + * @param The name of this Texture. + * @param The name of the TextureAtlas. + * @param The registration pivotX position. + * @param The registration pivotY position. + * @return An Object. + */ + public function getTextureDisplay(textureName:String, textureAtlasName:String = null, pivotX:Number = Number.NaN, pivotY:Number = Number.NaN):Object + { + var targetTextureAtlas:Object; + if(textureAtlasName) + { + targetTextureAtlas = textureAtlasDic[textureAtlasName]; + } + else + { + for (textureAtlasName in textureAtlasDic) + { + targetTextureAtlas = textureAtlasDic[textureAtlasName]; + if(targetTextureAtlas.getRegion(textureName)) + { + break; + } + targetTextureAtlas = null; + } + } + + if(!targetTextureAtlas) + { + return null; + } + + if(isNaN(pivotX) || isNaN(pivotY)) + { + //默认dragonBonesData的名字和和纹理集的名字是一致的 + var data:DragonBonesData = dragonBonesDataDic[textureAtlasName]; + data = data ? data : findFirstDragonBonesData(); + if(data) + { + var displayData:DisplayData = data.getDisplayDataByName(textureName); + if(displayData) + { + pivotX = displayData.pivot.x; + pivotY = displayData.pivot.y; + } + } + } + + return generateDisplay(targetTextureAtlas, textureName, pivotX, pivotY); + } + + //一般情况下dragonBonesData和textureAtlas是一对一的,通过相同的key对应。 + //TO DO 以后会支持一对多的情况 + public function buildArmature(armatureName:String, fromDragonBonesDataName:String = null, fromTextureAtlasName:String = null, skinName:String = null):Armature + { + var buildArmatureDataPackage:BuildArmatureDataPackage = new BuildArmatureDataPackage(); + if(fillBuildArmatureDataPackageArmatureInfo(armatureName, fromDragonBonesDataName, buildArmatureDataPackage)) + { + fillBuildArmatureDataPackageTextureInfo(fromTextureAtlasName, buildArmatureDataPackage); + } + + var dragonBonesData:DragonBonesData = buildArmatureDataPackage.dragonBonesData; + var armatureData:ArmatureData = buildArmatureDataPackage.armatureData; + var textureAtlas:Object = buildArmatureDataPackage.textureAtlas; + + if(!armatureData || !textureAtlas) + { + return null; + } + + return buildArmatureUsingArmatureDataFromTextureAtlas(dragonBonesData, armatureData, textureAtlas, skinName); + } + + public function buildFastArmature(armatureName:String, fromDragonBonesDataName:String = null, fromTextureAtlasName:String = null, skinName:String = null):FastArmature + { + var buildArmatureDataPackage:BuildArmatureDataPackage = new BuildArmatureDataPackage(); + if(fillBuildArmatureDataPackageArmatureInfo(armatureName, fromDragonBonesDataName, buildArmatureDataPackage)) + { + fillBuildArmatureDataPackageTextureInfo(fromTextureAtlasName, buildArmatureDataPackage); + } + + var dragonBonesData:DragonBonesData = buildArmatureDataPackage.dragonBonesData; + var armatureData:ArmatureData = buildArmatureDataPackage.armatureData; + var textureAtlas:Object = buildArmatureDataPackage.textureAtlas; + + if(!armatureData || !textureAtlas) + { + return null; + } + + return buildFastArmatureUsingArmatureDataFromTextureAtlas(dragonBonesData, armatureData, textureAtlas, skinName); + } + + protected function buildArmatureUsingArmatureDataFromTextureAtlas(dragonBonesData:DragonBonesData, armatureData:ArmatureData, textureAtlas:Object, skinName:String = null):Armature + { + var outputArmature:Armature = generateArmature(); + outputArmature.name = armatureData.name; + outputArmature.__dragonBonesData = dragonBonesData; + outputArmature._armatureData = armatureData; + outputArmature.animation.animationDataList = armatureData.animationDataList; + + buildBones(outputArmature); + //TO DO: Support multi textureAtlas case in future + buildSlots(outputArmature, skinName, textureAtlas); + + outputArmature.advanceTime(0); + return outputArmature; + } + + protected function buildFastArmatureUsingArmatureDataFromTextureAtlas(dragonBonesData:DragonBonesData, armatureData:ArmatureData, textureAtlas:Object, skinName:String = null):FastArmature + { + var outputArmature:FastArmature = generateFastArmature(); + outputArmature.name = armatureData.name; + outputArmature.__dragonBonesData = dragonBonesData; + outputArmature._armatureData = armatureData; + outputArmature.animation.animationDataList = armatureData.animationDataList; + + buildFastBones(outputArmature); + //TO DO: Support multi textureAtlas case in future + buildFastSlots(outputArmature, skinName, textureAtlas); + + outputArmature.advanceTime(0); + + return outputArmature; + } + + //暂时不支持ifRemoveOriginalAnimationList为false的情况 + public function copyAnimationsToArmature(toArmature:Armature, fromArmatreName:String, fromDragonBonesDataName:String = null, ifRemoveOriginalAnimationList:Boolean = true):Boolean + { + var buildArmatureDataPackage:BuildArmatureDataPackage = new BuildArmatureDataPackage(); + if(!fillBuildArmatureDataPackageArmatureInfo(fromArmatreName, fromDragonBonesDataName, buildArmatureDataPackage)) + { + return false; + } + + var fromArmatureData:ArmatureData = buildArmatureDataPackage.armatureData; + toArmature.animation.animationDataList = fromArmatureData.animationDataList; + + //处理子骨架的复制 + var fromSkinData:SkinData = fromArmatureData.getSkinData(""); + var fromSlotData:SlotData; + var fromDisplayData:DisplayData; + + var toSlotList:Vector. = toArmature.getSlots(false); + var toSlot:Slot; + var toSlotDisplayList:Array; + var toSlotDisplayListLength:uint; + var toDisplayObject:Object; + var toChildArmature:Armature; + + for each(toSlot in toSlotList) + { + toSlotDisplayList = toSlot.displayList; + toSlotDisplayListLength = toSlotDisplayList.length + for(var i:int = 0; i < toSlotDisplayListLength; i++) + { + toDisplayObject = toSlotDisplayList[i]; + + if(toDisplayObject is Armature) + { + toChildArmature = toDisplayObject as Armature; + + fromSlotData = fromSkinData.getSlotData(toSlot.name); + fromDisplayData = fromSlotData.displayDataList[i]; + if(fromDisplayData.type == DisplayData.ARMATURE) + { + copyAnimationsToArmature(toChildArmature, fromDisplayData.name, buildArmatureDataPackage.dragonBonesDataName, ifRemoveOriginalAnimationList); + } + } + } + } + + return true; + } + + private function fillBuildArmatureDataPackageArmatureInfo(armatureName:String, dragonBonesDataName:String, outputBuildArmatureDataPackage:BuildArmatureDataPackage):Boolean + { + if(dragonBonesDataName) + { + outputBuildArmatureDataPackage.dragonBonesDataName = dragonBonesDataName; + outputBuildArmatureDataPackage.dragonBonesData = dragonBonesDataDic[dragonBonesDataName]; + outputBuildArmatureDataPackage.armatureData = outputBuildArmatureDataPackage.dragonBonesData.getArmatureDataByName(armatureName); + } + else + { + for(dragonBonesDataName in dragonBonesDataDic) + { + outputBuildArmatureDataPackage.dragonBonesData = dragonBonesDataDic[dragonBonesDataName]; + outputBuildArmatureDataPackage.armatureData = outputBuildArmatureDataPackage.dragonBonesData.getArmatureDataByName(armatureName); + if(outputBuildArmatureDataPackage.armatureData) + { + outputBuildArmatureDataPackage.dragonBonesDataName = dragonBonesDataName; + return true; + } + } + } + return false; + } + + private function fillBuildArmatureDataPackageTextureInfo(fromTextureAtlasName:String, outputBuildArmatureDataPackage:BuildArmatureDataPackage):void + { + outputBuildArmatureDataPackage.textureAtlas = textureAtlasDic[fromTextureAtlasName ? fromTextureAtlasName : outputBuildArmatureDataPackage.dragonBonesDataName]; + } + + protected function findFirstDragonBonesData():DragonBonesData + { + for each(var outputDragonBonesData:DragonBonesData in dragonBonesDataDic) + { + if(outputDragonBonesData) + { + return outputDragonBonesData; + } + } + return null; + } + + protected function findFirstTextureAtlas():Object + { + for each(var outputTextureAtlas:Object in textureAtlasDic) + { + if(outputTextureAtlas) + { + return outputTextureAtlas; + } + } + return null; + } + + protected function buildBones(armature:Armature):void + { + //按照从属关系的顺序建立 + var boneDataList:Vector. = armature.armatureData.boneDataList; + + var boneData:BoneData; + var bone:Bone; + var parent:String; + for(var i:int = 0;i < boneDataList.length;i ++) + { + boneData = boneDataList[i]; + bone = Bone.initWithBoneData(boneData); + parent = boneData.parent; + if( parent && armature.armatureData.getBoneData(parent) == null) + { + parent = null; + } + armature.addBone(bone, parent, true); + } + armature.updateAnimationAfterBoneListChanged(); + } + + protected function buildFastBones(armature:FastArmature):void + { + //按照从属关系的顺序建立 + var boneDataList:Vector. = armature.armatureData.boneDataList; + + var boneData:BoneData; + var bone:FastBone; + for(var i:int = 0;i < boneDataList.length;i ++) + { + boneData = boneDataList[i]; + bone = FastBone.initWithBoneData(boneData); + armature.addBone(bone, boneData.parent); + } + } + + protected function buildFastSlots(armature:FastArmature, skinName:String, textureAtlas:Object):void + { + //根据皮肤初始化SlotData的DisplayDataList + var skinData:SkinData = armature.armatureData.getSkinData(skinName); + if(!skinData) + { + return; + } + armature.armatureData.setSkinData(skinName); + + var displayList:Array = []; + var slotDataList:Vector. = armature.armatureData.slotDataList; + var slotData:SlotData; + var slot:FastSlot; + for(var i:int = 0; i < slotDataList.length; i++) + { + displayList.length = 0; + slotData = slotDataList[i]; + slot = generateFastSlot(); + slot.initWithSlotData(slotData); + + var l:int = slotData.displayDataList.length; + while(l--) + { + var displayData:DisplayData = slotData.displayDataList[l]; + + switch(displayData.type) + { + case DisplayData.ARMATURE: + var childArmature:FastArmature = buildFastArmatureUsingArmatureDataFromTextureAtlas(armature.__dragonBonesData, armature.__dragonBonesData.getArmatureDataByName(displayData.name), textureAtlas, skinName); + displayList[l] = childArmature; + slot.hasChildArmature = true; + break; + + case DisplayData.IMAGE: + default: + displayList[l] = generateDisplay(textureAtlas, displayData.name, displayData.pivot.x, displayData.pivot.y); + break; + + } + } + //================================================== + //如果显示对象有name属性并且name属性可以设置的话,将name设置为与slot同名,dragonBones并不依赖这些属性,只是方便开发者 + for each(var displayObject:Object in displayList) + { + if(displayObject is FastArmature) + { + displayObject = (displayObject as FastArmature).display; + } + + if(displayObject.hasOwnProperty("name")) + { + try + { + displayObject["name"] = slot.name; + } + catch(err:Error) + { + } + } + } + //================================================== + slot.initDisplayList(displayList.concat()); + armature.addSlot(slot, slotData.parent); + slot.changeDisplayIndex(slotData.displayIndex); + } + } + + protected function buildSlots(armature:Armature, skinName:String, textureAtlas:Object):void + { + var skinData:SkinData = armature.armatureData.getSkinData(skinName); + if(!skinData) + { + return; + } + armature.armatureData.setSkinData(skinName); + var displayList:Array = []; + var slotDataList:Vector. = armature.armatureData.slotDataList; + var slotData:SlotData; + var slot:Slot; + var bone:Bone; + var skinListObject:Object = { }; + for(var i:int = 0; i < slotDataList.length; i++) + { + displayList.length = 0; + slotData = slotDataList[i]; + bone = armature.getBone(slotData.parent); + if(!bone) + { + continue; + } + + slot = generateSlot(); + slot.initWithSlotData(slotData); + bone.addSlot(slot); + + var l:int = slotData.displayDataList.length; + while(l--) + { + var displayData:DisplayData = slotData.displayDataList[l]; + + switch(displayData.type) + { + case DisplayData.ARMATURE: + var childArmature:Armature = buildArmatureUsingArmatureDataFromTextureAtlas(armature.__dragonBonesData, armature.__dragonBonesData.getArmatureDataByName(displayData.name), textureAtlas, skinName); + displayList[l] = childArmature; + break; + + case DisplayData.IMAGE: + default: + displayList[l] = generateDisplay(textureAtlas, displayData.name, displayData.pivot.x, displayData.pivot.y); + break; + + } + } + //================================================== + //如果显示对象有name属性并且name属性可以设置的话,将name设置为与slot同名,dragonBones并不依赖这些属性,只是方便开发者 + for each(var displayObject:Object in displayList) + { + if(displayObject is Armature) + { + displayObject = (displayObject as Armature).display; + } + + if(displayObject.hasOwnProperty("name")) + { + try + { + displayObject["name"] = slot.name; + } + catch(err:Error) + { + } + } + } + //================================================== + skinListObject[slotData.name] = displayList.concat(); + slot.displayList = displayList; + slot.changeDisplay(slotData.displayIndex); + } + armature.addSkinList(skinName, skinListObject); + } + + + public function addSkinToArmature(armature:Armature, skinName:String, textureAtlasName:String):void + { + var textureAtlas:Object = textureAtlasDic[textureAtlasName] + var skinData:SkinData = armature.armatureData.getSkinData(skinName); + if(!skinData || !textureAtlas) + { + return; + } + var displayList:Array = []; + var slotDataList:Vector. = armature.armatureData.slotDataList; + var slotData:SlotData; + var slot:Slot; + var bone:Bone; + var skinListData:Object = { }; + var displayDataList:Vector. + + for(var i:int = 0; i < slotDataList.length; i++) + { + displayList.length = 0; + slotData = slotDataList[i]; + bone = armature.getBone(slotData.parent); + if(!bone) + { + continue; + } + + var l:int = 0; + if (i >= skinData.slotDataList.length) + { + l = 0; + } + else + { + displayDataList = skinData.slotDataList[i].displayDataList; + l = displayDataList.length; + } + while(l--) + { + var displayData:DisplayData = displayDataList[l]; + + switch(displayData.type) + { + case DisplayData.ARMATURE: + var childArmature:Armature = buildArmatureUsingArmatureDataFromTextureAtlas(armature.__dragonBonesData, armature.__dragonBonesData.getArmatureDataByName(displayData.name), textureAtlas, skinName); + displayList[l] = childArmature; + break; + + case DisplayData.IMAGE: + default: + displayList[l] = generateDisplay(textureAtlas, displayData.name, displayData.pivot.x, displayData.pivot.y); + break; + + } + } + //================================================== + //如果显示对象有name属性并且name属性可以设置的话,将name设置为与slot同名,dragonBones并不依赖这些属性,只是方便开发者 + for each(var displayObject:Object in displayList) + { + if(displayObject is Armature) + { + displayObject = (displayObject as Armature).display; + } + + if(displayObject.hasOwnProperty("name")) + { + try + { + displayObject["name"] = slot.name; + } + catch(err:Error) + { + } + } + } + //================================================== + skinListData[slotData.name] = displayList.concat(); + } + armature.addSkinList(skinName, skinListData); + } + + /** + * Parses the raw data and returns a SkeletonData instance. + * @example + * + * import flash.events.Event; + * import dragonBones.factorys.NativeFactory; + * + * [Embed(source = "../assets/Dragon1.swf", mimeType = "application/octet-stream")] + * private static const ResourcesData:Class; + * var factory:NativeFactory = new NativeFactory(); + * factory.addEventListener(Event.COMPLETE, textureCompleteHandler); + * factory.parseData(new ResourcesData()); + * + * @param ByteArray. Represents the raw data for the whole DragonBones system. + * @param String. (optional) The SkeletonData instance name. + * @param Boolean. (optional) flag if delay animation data parsing. Delay animation data parsing can reduce the data paring time to improve loading performance. + * @param Dictionary. (optional) output parameter. If it is not null, and ifSkipAnimationData is true, it will be fulfilled animationData, so that developers can parse it later. + * @return A SkeletonData instance. + */ + public function parseData(bytes:ByteArray, dataName:String = null):void + { + if(!bytes) + { + throw new ArgumentError(); + } + + var decompressedData:DecompressedData = DataSerializer.decompressData(bytes); + + var dragonBonesData:DragonBonesData = DataParser.parseData(decompressedData.dragonBonesData); + decompressedData.name = dataName || dragonBonesData.name; + decompressedData.addEventListener(Event.COMPLETE, parseCompleteHandler); + decompressedData.parseTextureAtlasBytes(); + + addSkeletonData(dragonBonesData, dataName); + } + + /** @private */ + protected function parseCompleteHandler(event:Event):void + { + var decompressedData:DecompressedData = event.target as DecompressedData; + decompressedData.removeEventListener(Event.COMPLETE, parseCompleteHandler); + + var textureAtlas:Object = generateTextureAtlas(decompressedData.textureAtlas, decompressedData.textureAtlasData); + addTextureAtlas(textureAtlas, decompressedData.name); + + decompressedData.dispose(); + this.dispatchEvent(new Event(Event.COMPLETE)); + } + + + /** @private */ + protected function generateTextureAtlas(content:Object, textureAtlasRawData:Object):ITextureAtlas + { + return null; + } + + /** + * @private + * Generates an Armature instance. + * @return Armature An Armature instance. + */ + protected function generateArmature():Armature + { + return null; + } + + /** + * @private + * Generates an Armature instance. + * @return Armature An Armature instance. + */ + protected function generateFastArmature():FastArmature + { + return null; + } + + /** + * @private + * Generates an Slot instance. + * @return Slot An Slot instance. + */ + protected function generateSlot():Slot + { + return null; + } + + /** + * @private + * Generates an Slot instance. + * @return Slot An Slot instance. + */ + protected function generateFastSlot():FastSlot + { + return null; + } + + /** + * @private + * Generates a DisplayObject + * @param textureAtlas The TextureAtlas. + * @param fullName A qualified name. + * @param pivotX A pivot x based value. + * @param pivotY A pivot y based value. + * @return + */ + protected function generateDisplay(textureAtlas:Object, fullName:String, pivotX:Number, pivotY:Number):Object + { + return null; + } + + } +} +import dragonBones.objects.ArmatureData; +import dragonBones.objects.DragonBonesData; + +class BuildArmatureDataPackage +{ + public var dragonBonesDataName:String; + public var dragonBonesData:DragonBonesData; + public var armatureData:ArmatureData; + public var textureAtlas:Object; +} \ No newline at end of file diff --git a/srclib/dragonBones/factories/NativeFactory.as b/srclib/dragonBones/factories/NativeFactory.as new file mode 100644 index 00000000..65c5dcf8 --- /dev/null +++ b/srclib/dragonBones/factories/NativeFactory.as @@ -0,0 +1,168 @@ +package dragonBones.factories { + + import dragonBones.Armature; + import dragonBones.Slot; + import dragonBones.core.dragonBones_internal; + import dragonBones.display.NativeFastSlot; + import dragonBones.display.NativeSlot; + import dragonBones.fast.FastArmature; + import dragonBones.fast.FastSlot; + import dragonBones.textures.ITextureAtlas; + import dragonBones.textures.NativeTextureAtlas; + + import flash.display.MovieClip; + import flash.display.Shape; + import flash.display.Sprite; + import flash.geom.Rectangle; + + use namespace dragonBones_internal; + + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0, Flash 10 + * @langversion 3.0 + * @version 2.0 + */ + + public class NativeFactory extends BaseFactory + { + /** + * If enable BitmapSmooth + */ + public var fillBitmapSmooth:Boolean; + + /** + * If use bitmapData Texture(When using dbswf,you can use vector element,if enable useBitmapDataTexture,dbswf will be force converted to BitmapData) + */ + public var useBitmapDataTexture:Boolean; + + public function NativeFactory() + { + super(this); + } + + /** @private */ + override protected function generateTextureAtlas(content:Object, textureAtlasRawData:Object):ITextureAtlas + { + var textureAtlas:NativeTextureAtlas = new NativeTextureAtlas(content, textureAtlasRawData, 1, false); + return textureAtlas; + } + + /** @private */ + override protected function generateArmature():Armature + { + var display:Sprite = new Sprite(); + var armature:Armature = new Armature(display); + return armature; + } + + override protected function generateFastArmature():FastArmature + { + var armature:FastArmature = new FastArmature(new Sprite()); + return armature; + } + + override protected function generateFastSlot():FastSlot + { + var slot:FastSlot = new NativeFastSlot(); + return slot; + } + + /** @private */ + override protected function generateSlot():Slot + { + var slot:Slot = new NativeSlot(); + return slot; + } + + /** @private */ + override protected function generateDisplay(textureAtlas:Object, fullName:String, pivotX:Number, pivotY:Number):Object + { + var nativeTextureAtlas:NativeTextureAtlas; + if(textureAtlas is NativeTextureAtlas) + { + nativeTextureAtlas = textureAtlas as NativeTextureAtlas; + } + + if(nativeTextureAtlas) + { + var movieClip:MovieClip = nativeTextureAtlas.movieClip; + if(useBitmapDataTexture && movieClip) + { + nativeTextureAtlas.movieClipToBitmapData(); + } + + //TO DO 问春雷 + if (!useBitmapDataTexture && movieClip && movieClip.totalFrames >= 3) + { + movieClip.gotoAndStop(movieClip.totalFrames); + movieClip.gotoAndStop(fullName); + if (movieClip.numChildren > 0) + { + try + { + var displaySWF:Object = movieClip.getChildAt(0); + displaySWF.x = 0; + displaySWF.y = 0; + return displaySWF; + } + catch(e:Error) + { + throw new Error("Can not get the movie clip, please make sure the version of the resource compatible with app version!"); + } + } + } + else if(nativeTextureAtlas.bitmapData) + { + var subTextureRegion:Rectangle = nativeTextureAtlas.getRegion(fullName); + if (subTextureRegion) + { + var subTextureFrame:Rectangle = nativeTextureAtlas.getFrame(fullName); + + if (isNaN(pivotX) || isNaN(pivotX)) + { + if (subTextureFrame) + { + pivotX = subTextureFrame.width / 2 + subTextureFrame.x; + pivotY = subTextureFrame.height / 2 + subTextureFrame.y; + } + else + { + pivotX = subTextureRegion.width / 2; + pivotY = subTextureRegion.height / 2; + } + + } + else + { + if(subTextureFrame) + { + pivotX += subTextureFrame.x; + pivotY += subTextureFrame.y; + } + } + + var displayShape:Shape = new Shape(); + _helpMatrix.a = 1; + _helpMatrix.b = 0; + _helpMatrix.c = 0; + _helpMatrix.d = 1; + _helpMatrix.scale(1 / nativeTextureAtlas.scale, 1 / nativeTextureAtlas.scale); + _helpMatrix.tx = -pivotX - subTextureRegion.x; + _helpMatrix.ty = -pivotY - subTextureRegion.y; + + displayShape.graphics.beginBitmapFill(nativeTextureAtlas.bitmapData, _helpMatrix, false, fillBitmapSmooth); + displayShape.graphics.drawRect(-pivotX, -pivotY, subTextureRegion.width, subTextureRegion.height); + + return displayShape; + } + } + else + { + throw new Error(); + } + } + return null; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/factories/StarlingFactory.as b/srclib/dragonBones/factories/StarlingFactory.as new file mode 100644 index 00000000..82ec7095 --- /dev/null +++ b/srclib/dragonBones/factories/StarlingFactory.as @@ -0,0 +1,188 @@ +package dragonBones.factories { + + import dragonBones.Armature; + import dragonBones.Slot; + import dragonBones.core.dragonBones_internal; + import dragonBones.display.StarlingFastSlot; + import dragonBones.display.StarlingSlot; + import dragonBones.fast.FastArmature; + import dragonBones.fast.FastSlot; + import dragonBones.textures.ITextureAtlas; + import dragonBones.textures.StarlingTextureAtlas; + + import starling.core.Starling; + import starling.display.Image; + import starling.display.Sprite; + import starling.textures.SubTexture; + import starling.textures.Texture; + import starling.textures.TextureAtlas; + + import flash.display.BitmapData; + import flash.display.MovieClip; + import flash.geom.Rectangle; + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0, Flash 10 + * @langversion 3.0 + * @version 2.0 + */ + + use namespace dragonBones_internal; + + /** + * A object managing the set of armature resources for Starling engine. It parses the raw data, stores the armature resources and creates armature instances. + * @see dragonBones.Armature + */ + + /** + * A StarlingFactory instance manages the set of armature resources for the starling DisplayList. It parses the raw data (ByteArray), stores the armature resources and creates armature instances. + *

    Create an instance of the StarlingFactory class that way:

    + * + * import flash.events.Event; + * import dragonBones.factorys.BaseFactory; + * + * [Embed(source = "../assets/Dragon2.png", mimeType = "application/octet-stream")] + * private static const ResourcesData:Class; + * var factory:StarlingFactory = new StarlingFactory(); + * factory.addEventListener(Event.COMPLETE, textureCompleteHandler); + * factory.parseData(new ResourcesData()); + * + * @see dragonBones.Armature + */ + public class StarlingFactory extends BaseFactory + { + /** + * Whether to generate mapmaps (true) or not (false). + */ + public var generateMipMaps:Boolean; + /** + * Whether to optimize for rendering (true) or not (false). + */ + public var optimizeForRenderToTexture:Boolean; + /** + * Apply a scale for SWF specific texture. Use 1 for no scale. + */ + public var scaleForTexture:Number; + + /** + * Creates a new StarlingFactory instance. + */ + public function StarlingFactory() + { + super(this); + scaleForTexture = 1; + } + + /** @private */ + override protected function generateTextureAtlas(content:Object, textureAtlasRawData:Object):ITextureAtlas + { + var texture:Texture; + var bitmapData:BitmapData; + if (content is BitmapData) + { + bitmapData = content as BitmapData; + texture = Texture.fromBitmapData(bitmapData, generateMipMaps, optimizeForRenderToTexture); + } + else if (content is MovieClip) + { + var width:int = getNearest2N(content.width) * scaleForTexture; + var height:int = getNearest2N(content.height) * scaleForTexture; + +// _helpMatrix.a = 1; +// _helpMatrix.b = 0; +// _helpMatrix.c = 0; +// _helpMatrix.d = 1; + _helpMatrix.scale(scaleForTexture, scaleForTexture); + _helpMatrix.tx = 0; + _helpMatrix.ty = 0; + var movieClip:MovieClip = content as MovieClip; + movieClip.gotoAndStop(1); + bitmapData = new BitmapData(width, height, true, 0xFF00FF); + bitmapData.draw(movieClip, _helpMatrix); + movieClip.gotoAndStop(movieClip.totalFrames); + texture = Texture.fromBitmapData(bitmapData, generateMipMaps, optimizeForRenderToTexture, scaleForTexture); + } + else + { + throw new Error(); + } + var textureAtlas:StarlingTextureAtlas = new StarlingTextureAtlas(texture, textureAtlasRawData, false); + if (Starling.handleLostContext) + { + textureAtlas._bitmapData = bitmapData; + } + else + { + bitmapData.dispose(); + } + return textureAtlas; + } + + /** @private */ + override protected function generateArmature():Armature + { + var armature:Armature = new Armature(new Sprite()); + return armature; + } + + /** @private */ + override protected function generateFastArmature():FastArmature + { + var armature:FastArmature = new FastArmature(new Sprite()); + return armature; + } + + /** @private */ + override protected function generateSlot():Slot + { + var slot:Slot = new StarlingSlot(); + return slot; + } + + /** + * @private + * Generates an Slot instance. + * @return Slot An Slot instance. + */ + override protected function generateFastSlot():FastSlot + { + var slot:FastSlot = new StarlingFastSlot(); + return slot; + } + + /** @private */ + override protected function generateDisplay(textureAtlas:Object, fullName:String, pivotX:Number, pivotY:Number):Object + { + var subTexture:SubTexture = (textureAtlas as TextureAtlas).getTexture(fullName) as SubTexture; + if (subTexture) + { + var image:Image = new Image(subTexture); + if (isNaN(pivotX) || isNaN(pivotY)) + { + var subTextureFrame:Rectangle = (textureAtlas as TextureAtlas).getFrame(fullName); + if(subTextureFrame) + { + pivotX = subTextureFrame.width / 2;//pivotX; + pivotY = subTextureFrame.height / 2;// pivotY; + } + else + { + pivotX = subTexture.width / 2;//pivotX; + pivotY = subTexture.height / 2;// pivotY; + } + + } + image.pivotX = pivotX; + image.pivotY = pivotY; + + return image; + } + return null; + } + + private function getNearest2N(_n:uint):uint + { + return _n & _n - 1?1 << _n.toString(2).length:_n; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/fast/FastArmature.as b/srclib/dragonBones/fast/FastArmature.as new file mode 100644 index 00000000..5d92c973 --- /dev/null +++ b/srclib/dragonBones/fast/FastArmature.as @@ -0,0 +1,543 @@ +package dragonBones.fast { + + import dragonBones.cache.AnimationCacheManager; + import dragonBones.cache.SlotFrameCache; + import dragonBones.core.IArmature; + import dragonBones.core.ICacheableArmature; + import dragonBones.core.dragonBones_internal; + import dragonBones.events.FrameEvent; + import dragonBones.fast.animation.FastAnimation; + import dragonBones.fast.animation.FastAnimationState; + import dragonBones.objects.ArmatureData; + import dragonBones.objects.DragonBonesData; + import dragonBones.objects.Frame; + + import flash.events.Event; + import flash.events.EventDispatcher; + + use namespace dragonBones_internal; + + /** + * Dispatched when an animation state play complete (if playtimes equals to 0 means loop forever. Then this Event will not be triggered) + */ + [Event(name="complete", type="dragonBones.events.AnimationEvent")] + + /** + * Dispatched when an animation state complete a loop. + */ + [Event(name="loopComplete", type="dragonBones.events.AnimationEvent")] + + /** + * Dispatched when an animation state enter a frame with animation frame event. + */ + [Event(name="animationFrameEvent", type="dragonBones.events.FrameEvent")] + + /** + * Dispatched when an bone enter a frame with animation frame event. + */ + [Event(name="boneFrameEvent", type="dragonBones.events.FrameEvent")] + + /** + * 不支持动态添加Bone和Slot,换装请通过更换Slot的dispaly或子骨架childArmature来实现 + */ + public class FastArmature extends EventDispatcher implements ICacheableArmature + { + /** + * The name should be same with ArmatureData's name + */ + public var name:String; + /** + * An object that can contain any user extra data. + */ + public var userData:Object; + + + private var _enableCache:Boolean; + + /** + * 保证CacheManager是独占的前提下可以开启,开启后有助于性能提高 + */ + public var isCacheManagerExclusive:Boolean = false; + + /** @private */ + protected var _animation:FastAnimation; + + /** @private */ + protected var _display:Object; + + /** @private Store bones based on bones' hierarchy (From root to leaf)*/ + public var boneList:Vector.; + dragonBones_internal var _boneDic:Object; + + /** @private Store slots based on slots' zOrder*/ + public var slotList:Vector.; + dragonBones_internal var _slotDic:Object; + + public var slotHasChildArmatureList:Vector.; + + protected var _enableEventDispatch:Boolean = true; + + dragonBones_internal var __dragonBonesData:DragonBonesData; + dragonBones_internal var _armatureData:ArmatureData; + dragonBones_internal var _slotsZOrderChanged:Boolean; + + private var _eventList:Array; + private var _delayDispose:Boolean; + private var _lockDispose:Boolean; + private var useCache:Boolean = true; + public function FastArmature(display:Object) + { + super(this); + _display = display; + _animation = new FastAnimation(this); + _slotsZOrderChanged = false; + _armatureData = null; + + boneList = new Vector.; + _boneDic = {}; + slotList = new Vector.; + _slotDic = {}; + slotHasChildArmatureList = new Vector.; + + _eventList = []; + + _delayDispose = false; + _lockDispose = false; + + } + + /** + * Cleans up any resources used by this instance. + */ + public function dispose():void + { + _delayDispose = true; + if(!_animation || _lockDispose) + { + return; + } + + userData = null; + + _animation.dispose(); + var i:int = slotList.length; + while(i --) + { + slotList[i].dispose(); + } + i = boneList.length; + while(i --) + { + boneList[i].dispose(); + } + + slotList.fixed = false; + slotList.length = 0; + boneList.fixed = false; + boneList.length = 0; + + _armatureData = null; + _animation = null; + slotList = null; + boneList = null; + _eventList = null; + + } + + /** + * Update the animation using this method typically in an ENTERFRAME Event or with a Timer. + * @param The amount of second to move the playhead ahead. + */ + + public function advanceTime(passedTime:Number):void + { + _lockDispose = true; + _animation.advanceTime(passedTime); + + var bone:FastBone; + var slot:FastSlot; + var i:int; + if(_animation.animationState.isUseCache()) + { + if(!useCache) + { + useCache = true; + } + i = slotList.length; + while(i --) + { + slot = slotList[i]; + slot.updateByCache(); + } + } + else + { + if(useCache) + { + useCache = false; + i = slotList.length; + while(i --) + { + slot = slotList[i]; + slot.switchTransformToBackup(); + } + } + + i = boneList.length; + while(i --) + { + bone = boneList[i]; + bone.update(); + } + + i = slotList.length; + while(i --) + { + slot = slotList[i]; + slot.update(); + } + } + + i = slotHasChildArmatureList.length; + while(i--) + { + slot = slotHasChildArmatureList[i]; + var childArmature:IArmature = slot.childArmature as IArmature; + if(childArmature) + { + childArmature.advanceTime(passedTime); + } + } + + if(_slotsZOrderChanged) + { + updateSlotsZOrder(); + } + + while(_eventList.length > 0) + { + this.dispatchEvent(_eventList.shift()); + } + + _lockDispose = false; + if(_delayDispose) + { + dispose(); + } + } + + public function enableAnimationCache(frameRate:int, animationList:Array = null, loop:Boolean = true):AnimationCacheManager + { + var animationCacheManager:AnimationCacheManager = AnimationCacheManager.initWithArmatureData(armatureData,frameRate); + if(animationList) + { + for each(var animationName:String in animationList) + { + animationCacheManager.initAnimationCache(animationName); + } + } + else + { + animationCacheManager.initAllAnimationCache(); + } + animationCacheManager.setCacheGeneratorArmature(this); + animationCacheManager.generateAllAnimationCache(loop); + + animationCacheManager.bindCacheUserArmature(this); + enableCache = true; + return animationCacheManager; + } + + public function getBone(boneName:String):FastBone + { + return _boneDic[boneName]; + } + public function getSlot(slotName:String):FastSlot + { + return _slotDic[slotName]; + } + + /** + * Gets the Bone associated with this DisplayObject. + * @param Instance type of this object varies from flash.display.DisplayObject to startling.display.DisplayObject and subclasses. + * @return A Bone instance or null if no Bone with that DisplayObject exist.. + * @see dragonBones.Bone + */ + public function getBoneByDisplay(display:Object):FastBone + { + var slot:FastSlot = getSlotByDisplay(display); + return slot?slot.parent:null; + } + + /** + * Gets the Slot associated with this DisplayObject. + * @param Instance type of this object varies from flash.display.DisplayObject to startling.display.DisplayObject and subclasses. + * @return A Slot instance or null if no Slot with that DisplayObject exist. + * @see dragonBones.Slot + */ + public function getSlotByDisplay(displayObj:Object):FastSlot + { + if(displayObj) + { + for each(var slot:FastSlot in slotList) + { + if(slot.display == displayObj) + { + return slot; + } + } + } + return null; + } + + /** + * Get all Slot instance associated with this armature. + * @param if return Vector copy + * @return A Vector.<Slot> instance. + * @see dragonBones.Slot + */ + public function getSlots(returnCopy:Boolean = true):Vector. + { + return returnCopy?slotList.concat():slotList; + } + + dragonBones_internal function _updateBonesByCache():void + { + var i:int = boneList.length; + var bone:FastBone; + while(i --) + { + bone = boneList[i]; + bone.update(); + } + } + + + /** + * Add a Bone instance to this Armature instance. + * @param A Bone instance. + * @param (optional) The parent's name of this Bone instance. + * @see dragonBones.Bone + */ + dragonBones_internal function addBone(bone:FastBone, parentName:String = null):void + { + var parentBone:FastBone; + if(parentName) + { + parentBone = getBone(parentName); + parentBone.boneList.push(bone); + } + bone.armature = this; + bone.setParent(parentBone); + boneList.unshift(bone); + _boneDic[bone.name] = bone; + } + + /** + * Add a slot to a bone as child. + * @param slot A Slot instance + * @param boneName bone name + * @see dragonBones.core.DBObject + */ + dragonBones_internal function addSlot(slot:FastSlot, parentBoneName:String):void + { + var bone:FastBone = getBone(parentBoneName); + if(bone) + { + slot.armature = this; + slot.setParent(bone); + bone.slotList.push(slot); + slot.addDisplayToContainer(display); + slotList.push(slot); + _slotDic[slot.name] = slot; + if(slot.hasChildArmature) + { + slotHasChildArmatureList.push(slot); + } + + } + else + { + throw new ArgumentError(); + } + } + + /** + * Sort all slots based on zOrder + */ + dragonBones_internal function updateSlotsZOrder():void + { + slotList.fixed = false; + slotList.sort(sortSlot); + slotList.fixed = true; + var i:int = slotList.length; + while(i --) + { + var slot:FastSlot = slotList[i]; + if ((slot._frameCache && (slot._frameCache as SlotFrameCache).displayIndex >= 0) + || (!slot._frameCache && slot.displayIndex >= 0)) + { + slot.addDisplayToContainer(_display); + } + } + + _slotsZOrderChanged = false; + } + + private function sortBoneList():void + { + var i:int = boneList.length; + if(i == 0) + { + return; + } + var helpArray:Array = []; + while(i --) + { + var level:int = 0; + var bone:FastBone = boneList[i]; + var boneParent:FastBone = bone; + while(boneParent) + { + level ++; + boneParent = boneParent.parent; + } + helpArray[i] = [level, bone]; + } + + helpArray.sortOn("0", Array.NUMERIC|Array.DESCENDING); + + i = helpArray.length; + + boneList.fixed = false; + while(i --) + { + boneList[i] = helpArray[i][1]; + } + boneList.fixed = true; + + helpArray.length = 0; + } + + + + + + /** @private When AnimationState enter a key frame, call this func*/ + dragonBones_internal function arriveAtFrame(frame:Frame, animationState:FastAnimationState):void + { + if(frame.event && this.hasEventListener(FrameEvent.ANIMATION_FRAME_EVENT)) + { + var frameEvent:FrameEvent = new FrameEvent(FrameEvent.ANIMATION_FRAME_EVENT); + frameEvent.animationState = animationState; + frameEvent.frameLabel = frame.event; + addEvent(frameEvent); + } + + if(frame.action) + { + animation.gotoAndPlay(frame.action); + } + } + + /** + * Force update bones and slots. (When bone's animation play complete, it will not update) + */ + public function invalidUpdate(boneName:String = null):void + { + if(boneName) + { + var bone:FastBone = getBone(boneName); + if(bone) + { + bone.invalidUpdate(); + } + } + else + { + var i:int = boneList.length; + while(i --) + { + boneList[i].invalidUpdate(); + } + } + } + + public function resetAnimation():void + { + animation.animationState.resetTimelineStateList(); + for each(var boneItem:FastBone in boneList) + { + boneItem._timelineState = null; + } + animation.stop(); + } + + private function sortSlot(slot1:FastSlot, slot2:FastSlot):int + { + return slot1.zOrder < slot2.zOrder?1: -1; + } + + public function getAnimation():Object + { + return _animation; + } + + /** + * ArmatureData. + * @see dragonBones.objects.ArmatureData. + */ + public function get armatureData():ArmatureData + { + return _armatureData; + } + + /** + * An Animation instance + * @see dragonBones.animation.Animation + */ + public function get animation():FastAnimation + { + return _animation; + } + + /** + * Armature's display object. It's instance type depends on render engine. For example "flash.display.DisplayObject" or "startling.display.DisplayObject" + */ + public function get display():Object + { + return _display; + } + + public function get enableCache():Boolean + { + return _enableCache; + } + public function set enableCache(value:Boolean):void + { + _enableCache = value; + } + + public function get enableEventDispatch():Boolean + { + return _enableEventDispatch; + } + public function set enableEventDispatch(value:Boolean):void + { + _enableEventDispatch = value; + } + + public function getSlotDic():Object + { + return _slotDic; + } + + dragonBones_internal function addEvent(event:Event):void + { + if (_enableEventDispatch) + { + _eventList.push(event); + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/fast/FastBone.as b/srclib/dragonBones/fast/FastBone.as new file mode 100644 index 00000000..126cf7e6 --- /dev/null +++ b/srclib/dragonBones/fast/FastBone.as @@ -0,0 +1,185 @@ +package dragonBones.fast { + + import dragonBones.core.dragonBones_internal; + import dragonBones.events.FrameEvent; + import dragonBones.fast.animation.FastAnimationState; + import dragonBones.fast.animation.FastBoneTimelineState; + import dragonBones.objects.BoneData; + import dragonBones.objects.Frame; + + use namespace dragonBones_internal; + + /** + * 不保存子骨骼列表和子插槽列表 + * 不能动态添加子骨骼和子插槽 + */ + public class FastBone extends FastDBObject + { + public static function initWithBoneData(boneData:BoneData):FastBone + { + var outputBone:FastBone = new FastBone(); + + outputBone.name = boneData.name; + outputBone.inheritRotation = boneData.inheritRotation; + outputBone.inheritScale = boneData.inheritScale; + outputBone.origin.copy(boneData.transform); + + return outputBone; + } + + public var slotList:Vector. = new Vector.(); + public var boneList:Vector. = new Vector.(); + /** @private */ + dragonBones_internal var _timelineState:FastBoneTimelineState; + + /** @private */ + dragonBones_internal var _needUpdate:int; + + public function FastBone() + { + super(); + _needUpdate = 2; + } + + /** + * Get all Bone instance associated with this bone. + * @return A Vector.<Slot> instance. + * @see dragonBones.Slot + */ + public function getBones(returnCopy:Boolean = true):Vector. + { + return returnCopy?boneList.concat():boneList; + } + + /** + * Get all Slot instance associated with this bone. + * @return A Vector.<Slot> instance. + * @see dragonBones.Slot + */ + public function getSlots(returnCopy:Boolean = true):Vector. + { + return returnCopy?slotList.concat():slotList; + } + + /** + * @inheritDoc + */ + override public function dispose():void + { + super.dispose(); + _timelineState = null; + } + + //动画 + /** + * Force update the bone in next frame even if the bone is not moving. + */ + public function invalidUpdate():void + { + _needUpdate = 2; + } + + override protected function calculateRelativeParentTransform():void + { + _global.copy(this._origin); + if(_timelineState) + { + _global.add(_timelineState._transform); + } + } + + /** @private */ + override dragonBones_internal function updateByCache():void + { + super.updateByCache(); + _global = _frameCache.globalTransform; + _globalTransformMatrix = _frameCache.globalTransformMatrix; + } + + /** @private */ + dragonBones_internal function update(needUpdate:Boolean = false):void + { + _needUpdate --; + if(needUpdate || _needUpdate > 0 || (this._parent && this._parent._needUpdate > 0)) + { + _needUpdate = 1; + } + else + { + return; + } + + //计算global + updateGlobal(); + } + + /** @private When bone timeline enter a key frame, call this func*/ + dragonBones_internal function arriveAtFrame(frame:Frame, animationState:FastAnimationState):void + { + var childSlot:FastSlot; + if(frame.event && this.armature.hasEventListener(FrameEvent.BONE_FRAME_EVENT)) + { + var frameEvent:FrameEvent = new FrameEvent(FrameEvent.BONE_FRAME_EVENT); + frameEvent.bone = this; + frameEvent.animationState = animationState; + frameEvent.frameLabel = frame.event; + this.armature.addEvent(frameEvent); + } + } + + /** + * Unrecommended API. Recommend use slot.childArmature. + */ + public function get childArmature():Object + { + var s:FastSlot = slot; + if(s) + { + return s.childArmature; + } + return null; + } + + /** + * Unrecommended API. Recommend use slot.display. + */ + public function get display():Object + { + var s:FastSlot = slot; + if(s) + { + return s.display; + } + return null; + } + public function set display(value:Object):void + { + var s:FastSlot = slot; + if(s) + { + s.display = value; + } + } + + /** @private */ + override public function set visible(value:Boolean):void + { + if(this._visible != value) + { + this._visible = value; + for each(var childSlot:FastSlot in armature.slotList) + { + if(childSlot.parent == this) + { + childSlot.updateDisplayVisible(this._visible); + } + } + } + } + + public function get slot():FastSlot + { + return slotList.length > 0? slotList[0]:null; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/fast/FastDBObject.as b/srclib/dragonBones/fast/FastDBObject.as new file mode 100644 index 00000000..fe0ab8cf --- /dev/null +++ b/srclib/dragonBones/fast/FastDBObject.as @@ -0,0 +1,260 @@ +package dragonBones.fast { + + import dragonBones.cache.FrameCache; + import dragonBones.core.DBObject; + import dragonBones.core.dragonBones_internal; + import dragonBones.objects.DBTransform; + import dragonBones.utils.TransformUtil; + + import flash.geom.Matrix; + + use namespace dragonBones_internal; + + + public class FastDBObject + { + private var _name:String; + + /** + * An object that can contain any user extra data. + */ + public var userData:Object; + + /** + * + */ + public var inheritRotation:Boolean; + + /** + * + */ + public var inheritScale:Boolean; + + /** + * + */ + public var inheritTranslation:Boolean; + + + + /** @private */ + dragonBones_internal var _global:DBTransform; + /** @private */ + dragonBones_internal var _globalTransformMatrix:Matrix; + + /** @private */ + dragonBones_internal var _globalBackup:DBTransform; + /** @private */ + dragonBones_internal var _globalTransformMatrixBackup:Matrix; + + dragonBones_internal static var _tempParentGlobalTransform:DBTransform = new DBTransform(); + + dragonBones_internal var _frameCache:FrameCache; + + /** @private */ + dragonBones_internal function updateByCache():void + { + _global = _frameCache.globalTransform; + _globalTransformMatrix = _frameCache.globalTransformMatrix; + } + + /** @private */ + dragonBones_internal function switchTransformToBackup():void + { + if(!_globalBackup) + { + _globalBackup = new DBTransform(); + _globalTransformMatrixBackup = new Matrix(); + } + _global = _globalBackup; + _globalTransformMatrix = _globalTransformMatrixBackup; + } + + /** + * The armature this DBObject instance belongs to. + */ + public var armature:FastArmature; + + /** @private */ + protected var _origin:DBTransform; + + /** @private */ + protected var _visible:Boolean; + + /** @private */ + dragonBones_internal var _parent:FastBone; + + /** @private */ + dragonBones_internal function setParent(value:FastBone):void + { + _parent = value; + } + + public function FastDBObject() + { + _globalTransformMatrix = new Matrix(); + + _global = new DBTransform(); + _origin = new DBTransform(); + + _visible = true; + + armature = null; + _parent = null; + + userData = null; + + this.inheritRotation = true; + this.inheritScale = true; + this.inheritTranslation = true; + } + + /** + * Cleans up any resources used by this DBObject instance. + */ + public function dispose():void + { + userData = null; + + _globalTransformMatrix = null; + _global = null; + _origin = null; + + armature = null; + _parent = null; + } + + static private var tempOutputObj:Object = {}; + protected function calculateParentTransform():Object + { + if(this.parent && (this.inheritTranslation || this.inheritRotation || this.inheritScale)) + { + var parentGlobalTransform:DBTransform = this._parent._global; + var parentGlobalTransformMatrix:Matrix = this._parent._globalTransformMatrix; + + if( !this.inheritTranslation && (parentGlobalTransform.x != 0 || parentGlobalTransform.y != 0) || + !this.inheritRotation && (parentGlobalTransform.skewX != 0 || parentGlobalTransform.skewY != 0) || + !this.inheritScale && (parentGlobalTransform.scaleX != 1 || parentGlobalTransform.scaleY != 1)) + { + parentGlobalTransform = FastDBObject._tempParentGlobalTransform; + parentGlobalTransform.copy(this._parent._global); + if(!this.inheritTranslation) + { + parentGlobalTransform.x = 0; + parentGlobalTransform.y = 0; + } + if(!this.inheritScale) + { + parentGlobalTransform.scaleX = 1; + parentGlobalTransform.scaleY = 1; + } + if(!this.inheritRotation) + { + parentGlobalTransform.skewX = 0; + parentGlobalTransform.skewY = 0; + } + + parentGlobalTransformMatrix = DBObject._tempParentGlobalTransformMatrix; + TransformUtil.transformToMatrix(parentGlobalTransform, parentGlobalTransformMatrix); + } + tempOutputObj.parentGlobalTransform = parentGlobalTransform; + tempOutputObj.parentGlobalTransformMatrix = parentGlobalTransformMatrix; + return tempOutputObj; + } + return null; + } + + protected function updateGlobal():Object + { + calculateRelativeParentTransform(); + var output:Object = calculateParentTransform(); + if(output != null) + { + //计算父骨头绝对坐标 + var parentMatrix:Matrix = output.parentGlobalTransformMatrix; + var parentGlobalTransform:DBTransform = output.parentGlobalTransform; + //计算绝对坐标 + var x:Number = _global.x; + var y:Number = _global.y; + + _global.x = parentMatrix.a * x + parentMatrix.c * y + parentMatrix.tx; + _global.y = parentMatrix.d * y + parentMatrix.b * x + parentMatrix.ty; + + if(this.inheritRotation) + { + _global.skewX += parentGlobalTransform.skewX; + _global.skewY += parentGlobalTransform.skewY; + } + + if(this.inheritScale) + { + _global.scaleX *= parentGlobalTransform.scaleX; + _global.scaleY *= parentGlobalTransform.scaleY; + } + } + TransformUtil.transformToMatrix(_global, _globalTransformMatrix); + return output; + } + + protected function calculateRelativeParentTransform():void + { + } + + public function get name():String + { + return _name; + } + public function set name(value:String):void + { + _name = value; + } + + /** + * This DBObject instance global transform instance. + * @see dragonBones.objects.DBTransform + */ + public function get global():DBTransform + { + return _global; + } + + + public function get globalTransformMatrix():Matrix + { + return _globalTransformMatrix; + } + + /** + * This DBObject instance related to parent transform instance. + * @see dragonBones.objects.DBTransform + */ + public function get origin():DBTransform + { + return _origin; + } + + /** + * Indicates the Bone instance that directly contains this DBObject instance if any. + */ + public function get parent():FastBone + { + return _parent; + } + + /** @private */ + + public function get visible():Boolean + { + return _visible; + } + public function set visible(value:Boolean):void + { + _visible = value; + } + + public function set frameCache(cache:FrameCache):void + { + _frameCache = cache; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/fast/FastSlot.as b/srclib/dragonBones/fast/FastSlot.as new file mode 100644 index 00000000..86421704 --- /dev/null +++ b/srclib/dragonBones/fast/FastSlot.as @@ -0,0 +1,539 @@ +package dragonBones.fast { + + import dragonBones.cache.SlotFrameCache; + import dragonBones.core.IArmature; + import dragonBones.core.ISlotCacheGenerator; + import dragonBones.core.dragonBones_internal; + import dragonBones.fast.animation.FastAnimationState; + import dragonBones.objects.DisplayData; + import dragonBones.objects.Frame; + import dragonBones.objects.SlotData; + import dragonBones.objects.SlotFrame; + import dragonBones.utils.ColorTransformUtil; + import dragonBones.utils.TransformUtil; + + import flash.errors.IllegalOperationError; + import flash.geom.ColorTransform; + import flash.geom.Matrix; + + use namespace dragonBones_internal; + + public class FastSlot extends FastDBObject implements ISlotCacheGenerator + { + /** @private Need to keep the reference of DisplayData. When slot switch displayObject, it need to restore the display obect's origional pivot. */ + dragonBones_internal var _displayDataList:Vector.; + /** @private */ + dragonBones_internal var _originZOrder:Number; + /** @private */ + dragonBones_internal var _tweenZOrder:Number; + /** @private */ + protected var _offsetZOrder:Number; + + protected var _displayList:Array; + protected var _currentDisplayIndex:int; + dragonBones_internal var _colorTransform:ColorTransform; + dragonBones_internal var _isColorChanged:Boolean; + protected var _currentDisplay:Object; + + protected var _blendMode:String; + + public var hasChildArmature:Boolean; + public function FastSlot(self:FastSlot) + { + super(); + + if(self != this) + { + throw new IllegalOperationError("Abstract class can not be instantiated!"); + } + hasChildArmature = false; + _currentDisplayIndex = -1; + + _originZOrder = 0; + _tweenZOrder = 0; + _offsetZOrder = 0; + _colorTransform = new ColorTransform(); + _isColorChanged = false; + _displayDataList = null; + _currentDisplay = null; + + this.inheritRotation = true; + this.inheritScale = true; + } + + public function initWithSlotData(slotData:SlotData):void + { + name = slotData.name; + blendMode = slotData.blendMode; + _originZOrder = slotData.zOrder; + _displayDataList = slotData.displayDataList; + } + + /** + * @inheritDoc + */ + override public function dispose():void + { + if(!_displayList) + { + return; + } + + super.dispose(); + + _displayDataList = null; + _displayList = null; + _currentDisplay = null; + } + + //动画 + /** @private */ + override dragonBones_internal function updateByCache():void + { + super.updateByCache(); + updateTransform(); + //颜色 + var cacheColor:ColorTransform = (this._frameCache as SlotFrameCache).colorTransform; + var cacheColorChanged:Boolean = cacheColor != null; + if( this.colorChanged != cacheColorChanged || + (this.colorChanged && cacheColorChanged && !ColorTransformUtil.isEqual(_colorTransform, cacheColor))) + { + cacheColor = cacheColor || ColorTransformUtil.originalColor; + updateDisplayColor( cacheColor.alphaOffset, + cacheColor.redOffset, + cacheColor.greenOffset, + cacheColor.blueOffset, + cacheColor.alphaMultiplier, + cacheColor.redMultiplier, + cacheColor.greenMultiplier, + cacheColor.blueMultiplier, + cacheColorChanged); + } + + //displayIndex + changeDisplayIndex((this._frameCache as SlotFrameCache).displayIndex); + } + + /** @private */ + dragonBones_internal function update():void + { + if(this._parent._needUpdate <= 0) + { + return; + } + + updateGlobal(); + updateTransform(); + } + + override protected function calculateRelativeParentTransform():void + { + _global.copy(this._origin); + } + + dragonBones_internal function initDisplayList(newDisplayList:Array):void + { + this._displayList = newDisplayList; + } + + private function clearCurrentDisplay():int + { + if(hasChildArmature) + { + var targetArmature:IArmature = this.childArmature as IArmature; + if(targetArmature) + { + targetArmature.resetAnimation() + } + } + if (_isColorChanged) + { + updateDisplayColor(0, 0, 0, 0, 1, 1, 1, 1, true); + } + var slotIndex:int = getDisplayIndex(); + removeDisplayFromContainer(); + return slotIndex; + } + + /** @private */ + dragonBones_internal function changeDisplayIndex(displayIndex:int):void + { + if(_currentDisplayIndex == displayIndex) + { + return; + } + + var slotIndex:int = -1; + + if(_currentDisplayIndex >=0) + { + slotIndex = clearCurrentDisplay(); + } + + _currentDisplayIndex = displayIndex; + + if(_currentDisplayIndex >=0) + { + this._origin.copy(_displayDataList[_currentDisplayIndex].transform); + this.initCurrentDisplay(slotIndex); + } + } + + //currentDisplayIndex不变,改变内容,必须currentDisplayIndex >=0 + private function changeSlotDisplay(value:Object):void + { + var slotIndex:int = clearCurrentDisplay(); + _displayList[_currentDisplayIndex] = value; + this.initCurrentDisplay(slotIndex); + } + + private function initCurrentDisplay(slotIndex:int):void + { + var display:Object = _displayList[_currentDisplayIndex]; + if (display) + { + if(display is FastArmature) + { + _currentDisplay = (display as FastArmature).display; + } + else + { + _currentDisplay = display; + } + } + else + { + _currentDisplay = null; + } + + updateDisplay(_currentDisplay); + if(_currentDisplay) + { + if(slotIndex != -1) + { + addDisplayToContainer(this.armature.display, slotIndex); + } + else + { + this.armature._slotsZOrderChanged = true; + addDisplayToContainer(this.armature.display); + } + + if(_blendMode) + { + updateDisplayBlendMode(_blendMode); + } + if(_isColorChanged) + { + updateDisplayColor( _colorTransform.alphaOffset, + _colorTransform.redOffset, + _colorTransform.greenOffset, + _colorTransform.blueOffset, + _colorTransform.alphaMultiplier, + _colorTransform.redMultiplier, + _colorTransform.greenMultiplier, + _colorTransform.blueMultiplier, + true); + } + updateTransform(); + + if(display is FastArmature) + { + var targetArmature:FastArmature = display as FastArmature; + + if( this.armature && + this.armature.animation.animationState && + targetArmature.animation.hasAnimation(this.armature.animation.animationState.name)) + { + targetArmature.animation.gotoAndPlay(this.armature.animation.animationState.name); + } + else + { + targetArmature.animation.play(); + } + } + } + } + + /** @private */ + override public function set visible(value:Boolean):void + { + if(this._visible != value) + { + this._visible = value; + updateDisplayVisible(this._visible); + } + } + + /** + * The DisplayObject list belonging to this Slot instance (display or armature). Replace it to implement switch texture. + */ + public function get displayList():Array + { + return _displayList; + } + public function set displayList(value:Array):void + { + //todo: 考虑子骨架变化的各种情况 + if(!value) + { + throw new ArgumentError(); + } + + var newDisplay:Object = value[_currentDisplayIndex]; + var displayChanged:Boolean = _currentDisplayIndex >= 0 && _displayList[_currentDisplayIndex] != newDisplay; + + _displayList = value; + + if(displayChanged) + { + changeSlotDisplay(newDisplay); + } + } + + /** + * The DisplayObject belonging to this Slot instance. Instance type of this object varies from flash.display.DisplayObject to startling.display.DisplayObject and subclasses. + */ + public function get display():Object + { + return _currentDisplay; + } + public function set display(value:Object):void + { + //todo: 考虑子骨架变化的各种情况进行进一步测试 + if (_currentDisplayIndex < 0) + { + _currentDisplayIndex = 0; + } + if(_displayList[_currentDisplayIndex] == value) + { + return; + } + + changeSlotDisplay(value); + } + + /** + * The sub-armature of this Slot instance. + */ + public function get childArmature():Object + { + return _displayList[_currentDisplayIndex] is IArmature ? _displayList[_currentDisplayIndex] : null; + } + + public function set childArmature(value:Object):void + { + display = value; + } + /** + * zOrder. Support decimal for ensure dynamically added slot work toghther with animation controled slot. + * @return zOrder. + */ + public function get zOrder():Number + { + return _originZOrder + _tweenZOrder + _offsetZOrder; + } + public function set zOrder(value:Number):void + { + if(zOrder != value) + { + _offsetZOrder = value - _originZOrder - _tweenZOrder; + if(this.armature) + { + this.armature._slotsZOrderChanged = true; + } + } + } + + /** + * blendMode + * @return blendMode. + */ + public function get blendMode():String + { + return _blendMode; + } + public function set blendMode(value:String):void + { + if(_blendMode != value) + { + _blendMode = value; + updateDisplayBlendMode(_blendMode); + } + } + + /** + * Indicates the Bone instance that directly contains this DBObject instance if any. + */ + public function get colorTransform():ColorTransform + { + return _colorTransform; + } + + public function get displayIndex():int + { + return _currentDisplayIndex; + } + + public function get colorChanged():Boolean + { + return _isColorChanged; + } + + //Abstract method + /** + * @private + */ + dragonBones_internal function updateDisplay(value:Object):void + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + */ + dragonBones_internal function getDisplayIndex():int + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + * Adds the original display object to another display object. + * @param container + * @param index + */ + dragonBones_internal function addDisplayToContainer(container:Object, index:int = -1):void + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + * remove the original display object from its parent. + */ + dragonBones_internal function removeDisplayFromContainer():void + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + * Updates the transform of the slot. + */ + dragonBones_internal function updateTransform():void + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + */ + dragonBones_internal function updateDisplayVisible(value:Boolean):void + { + /** + * bone.visible && slot.visible && updateVisible + * this._parent.visible && this._visible && value; + */ + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** + * @private + * Updates the color of the display object. + * @param a + * @param r + * @param g + * @param b + * @param aM + * @param rM + * @param gM + * @param bM + */ + dragonBones_internal function updateDisplayColor( + aOffset:Number, + rOffset:Number, + gOffset:Number, + bOffset:Number, + aMultiplier:Number, + rMultiplier:Number, + gMultiplier:Number, + bMultiplier:Number, + colorChanged:Boolean = false + ):void + { + _colorTransform.alphaOffset = aOffset; + _colorTransform.redOffset = rOffset; + _colorTransform.greenOffset = gOffset; + _colorTransform.blueOffset = bOffset; + _colorTransform.alphaMultiplier = aMultiplier; + _colorTransform.redMultiplier = rMultiplier; + _colorTransform.greenMultiplier = gMultiplier; + _colorTransform.blueMultiplier = bMultiplier; + _isColorChanged = colorChanged; + } + + /** + * @private + * Update the blend mode of the display object. + * @param value The blend mode to use. + */ + dragonBones_internal function updateDisplayBlendMode(value:String):void + { + throw new IllegalOperationError("Abstract method needs to be implemented in subclass!"); + } + + /** @private When slot timeline enter a key frame, call this func*/ + dragonBones_internal function arriveAtFrame(frame:Frame, animationState:FastAnimationState):void + { + var slotFrame:SlotFrame = frame as SlotFrame; + var displayIndex:int = slotFrame.displayIndex; + changeDisplayIndex(displayIndex); + updateDisplayVisible(slotFrame.visible); + if(displayIndex >= 0) + { + if(!isNaN(slotFrame.zOrder) && slotFrame.zOrder != _tweenZOrder) + { + _tweenZOrder = slotFrame.zOrder; + this.armature._slotsZOrderChanged = true; + } + } + //[TODO]currently there is only gotoAndPlay belongs to frame action. In future, there will be more. + //后续会扩展更多的action,目前只有gotoAndPlay的含义 + if(frame.action) + { + var targetArmature:IArmature = childArmature as IArmature; + if (targetArmature) + { + targetArmature.getAnimation().gotoAndPlay(frame.action); + } + } + } + + /** @private */ + dragonBones_internal function hideSlots():void + { + changeDisplayIndex( -1); + removeDisplayFromContainer(); + if (_frameCache) + { + this._frameCache.clear(); + } + } + + override protected function updateGlobal():Object + { + calculateRelativeParentTransform(); + TransformUtil.transformToMatrix(_global, _globalTransformMatrix); + var output:Object = calculateParentTransform(); + if(output != null) + { + //计算父骨头绝对坐标 + var parentMatrix:Matrix = output.parentGlobalTransformMatrix; + _globalTransformMatrix.concat(parentMatrix); + } + TransformUtil.matrixToTransform(_globalTransformMatrix,_global,true,true); + return output; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/fast/animation/FastAnimation.as b/srclib/dragonBones/fast/animation/FastAnimation.as new file mode 100644 index 00000000..a5ab3469 --- /dev/null +++ b/srclib/dragonBones/fast/animation/FastAnimation.as @@ -0,0 +1,275 @@ +package dragonBones.fast.animation { + + import dragonBones.cache.AnimationCacheManager; + import dragonBones.core.IArmature; + import dragonBones.core.dragonBones_internal; + import dragonBones.fast.FastArmature; + import dragonBones.fast.FastSlot; + import dragonBones.objects.AnimationData; + + use namespace dragonBones_internal; + + /** + * 不支持动画融合,在开启缓存的情况下,不支持无极的平滑补间 + */ + public class FastAnimation + { + public var animationList:Vector.; + public var animationState:FastAnimationState = new FastAnimationState(); + public var animationCacheManager:AnimationCacheManager; + + private var _armature:FastArmature; + private var _animationDataList:Vector.; + private var _animationDataObj:Object; + private var _isPlaying:Boolean; + private var _timeScale:Number; + + public function FastAnimation(armature:FastArmature) + { + _armature = armature; + animationState._armature = armature; + animationList = new Vector.; + _animationDataObj = {}; + + _isPlaying = false; + _timeScale = 1; + } + + /** + * Qualifies all resources used by this Animation instance for garbage collection. + */ + public function dispose():void + { + if(!_armature) + { + return; + } + + _armature = null; + _animationDataList = null; + animationList = null; + animationState = null; + } + + public function gotoAndPlay( animationName:String, fadeInTime:Number = -1, duration:Number = -1, playTimes:Number = Number.NaN):FastAnimationState + { + if (!_animationDataList) + { + return null; + } + var animationData:AnimationData = _animationDataObj[animationName]; + if (!animationData) + { + return null; + } + _isPlaying = true; + fadeInTime = fadeInTime < 0?(animationData.fadeTime < 0?0.3:animationData.fadeTime):fadeInTime; + var durationScale:Number; + if(duration < 0) + { + durationScale = animationData.scale < 0?1:animationData.scale; + } + else + { + durationScale = duration * 1000 / animationData.duration; + } + playTimes = isNaN(playTimes)?animationData.playTimes:playTimes; + + //播放新动画 + + animationState.fadeIn(animationData, playTimes, 1 / durationScale, fadeInTime); + + if(_armature.enableCache && animationCacheManager) + { + animationState.animationCache = animationCacheManager.getAnimationCache(animationName); + } + + var i:int = _armature.slotHasChildArmatureList.length; + while(i--) + { + var slot:FastSlot = _armature.slotHasChildArmatureList[i]; + var childArmature:IArmature = slot.childArmature as IArmature; + if(childArmature) + { + childArmature.getAnimation().gotoAndPlay(animationName); + } + } + return animationState; + } + + /** + * Control the animation to stop with a specified time. If related animationState haven't been created, then create a new animationState. + * @param animationName The name of the animationState. + * @param time + * @param normalizedTime + * @param fadeInTime A fade time to apply (>= 0), -1 means use xml data's fadeInTime. + * @param duration The duration of that Animation. -1 means use xml data's duration. + * @param layer The layer of the animation. + * @param group The group of the animation. + * @param fadeOutMode Fade out mode (none, sameLayer, sameGroup, sameLayerAndGroup, all). + * @return AnimationState. + * @see dragonBones.objects.AnimationData. + * @see dragonBones.animation.AnimationState. + */ + public function gotoAndStop( + animationName:String, + time:Number, + normalizedTime:Number = -1, + fadeInTime:Number = 0, + duration:Number = -1 + ):FastAnimationState + { + if(!animationState.name != animationName) + { + gotoAndPlay(animationName, fadeInTime, duration); + } + + if(normalizedTime >= 0) + { + animationState.setCurrentTime(animationState.totalTime * normalizedTime); + } + else + { + animationState.setCurrentTime(time); + } + + animationState.stop(); + return animationState; + } + + /** + * Play the animation from the current position. + */ + public function play():void + { + if(!_animationDataList) + { + return; + } + if(!animationState.name) + { + gotoAndPlay(_animationDataList[0].name); + } + else if (!_isPlaying) + { + _isPlaying = true; + } + else + { + gotoAndPlay(animationState.name); + } + } + + public function stop():void + { + _isPlaying = false; + } + + /** @private */ + dragonBones_internal function advanceTime(passedTime:Number):void + { + if(!_isPlaying) + { + return; + } + + animationState.advanceTime(passedTime * _timeScale); + } + + /** + * check if contains a AnimationData by name. + * @return Boolean. + * @see dragonBones.animation.AnimationData. + */ + public function hasAnimation(animationName:String):Boolean + { + return _animationDataObj[animationName] != null; + } + + /** + * The amount by which passed time should be scaled. Used to slow down or speed up animations. Defaults to 1. + */ + public function get timeScale():Number + { + return _timeScale; + } + public function set timeScale(value:Number):void + { + if(isNaN(value) || value < 0) + { + value = 1; + } + _timeScale = value; + } + + /** + * The AnimationData list associated with this Animation instance. + * @see dragonBones.objects.AnimationData. + */ + public function get animationDataList():Vector. + { + return _animationDataList; + } + public function set animationDataList(value:Vector.):void + { + _animationDataList = value; + animationList.length = 0; + for each(var animationData:AnimationData in _animationDataList) + { + animationList.push(animationData.name); + _animationDataObj[animationData.name] = animationData; + } + } + + /** + * Unrecommended API. Recommend use animationList. + */ + public function get movementList():Vector. + { + return animationList; + } + + /** + * Unrecommended API. Recommend use lastAnimationName. + */ + public function get movementID():String + { + return lastAnimationName; + } + + /** + * Is the animation playing. + * @see dragonBones.animation.AnimationState. + */ + public function get isPlaying():Boolean + { + return _isPlaying && !isComplete; + } + + /** + * Is animation complete. + * @see dragonBones.animation.AnimationState. + */ + public function get isComplete():Boolean + { + return animationState.isComplete; + } + + /** + * The last AnimationState this Animation played. + * @see dragonBones.objects.AnimationData. + */ + public function get lastAnimationState():FastAnimationState + { + return animationState; + } + /** + * The name of the last AnimationData played. + * @see dragonBones.objects.AnimationData. + */ + public function get lastAnimationName():String + { + return animationState?animationState.name:null; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/fast/animation/FastAnimationState.as b/srclib/dragonBones/fast/animation/FastAnimationState.as new file mode 100644 index 00000000..a98f62f9 --- /dev/null +++ b/srclib/dragonBones/fast/animation/FastAnimationState.as @@ -0,0 +1,504 @@ +package dragonBones.fast.animation { + + import dragonBones.cache.AnimationCache; + import dragonBones.core.IAnimationState; + import dragonBones.core.dragonBones_internal; + import dragonBones.events.AnimationEvent; + import dragonBones.fast.FastArmature; + import dragonBones.fast.FastBone; + import dragonBones.fast.FastSlot; + import dragonBones.objects.AnimationData; + import dragonBones.objects.Frame; + import dragonBones.objects.SlotTimeline; + import dragonBones.objects.TransformTimeline; + + use namespace dragonBones_internal; + + public class FastAnimationState implements IAnimationState + { + + public var animationCache:AnimationCache; + /** + * If auto genterate tween between keyframes. + */ + public var autoTween:Boolean; + private var _progress:Number; + + dragonBones_internal var _armature:FastArmature; + + private var _boneTimelineStateList:Vector. = new Vector.; + private var _slotTimelineStateList:Vector. = new Vector.; + public var animationData:AnimationData; + + public var name:String; + private var _time:Number;//秒 + private var _currentFrameIndex:int; + private var _currentFramePosition:int; + private var _currentFrameDuration:int; + + private var _currentPlayTimes:int; + private var _totalTime:int;//毫秒 + private var _currentTime:int; + private var _lastTime:int; + + private var _isComplete:Boolean; + private var _isPlaying:Boolean; + private var _timeScale:Number; + private var _playTimes:int; + + private var _fading:Boolean = false; + private var _listenCompleteEvent:Boolean; + private var _listenLoopCompleteEvent:Boolean; + + dragonBones_internal var _fadeTotalTime:Number; + + + public function FastAnimationState() + { + } + + public function dispose():void + { + resetTimelineStateList(); + _armature = null; + } + + /** + * Play the current animation. 如果动画已经播放完毕, 将不会继续播放. + */ + public function play():FastAnimationState + { + _isPlaying = true; + return this; + } + + /** + * Stop playing current animation. + */ + public function stop():FastAnimationState + { + _isPlaying = false; + return this; + } + + public function setCurrentTime(value:Number):FastAnimationState + { + if(value < 0 || isNaN(value)) + { + value = 0; + } + _time = value; + _currentTime = _time * 1000; + return this; + } + + dragonBones_internal function resetTimelineStateList():void + { + var i:int = _boneTimelineStateList.length; + while(i --) + { + FastBoneTimelineState.returnObject(_boneTimelineStateList[i]); + } + _boneTimelineStateList.length = 0; + + i = _slotTimelineStateList.length; + while(i --) + { + FastSlotTimelineState.returnObject(_slotTimelineStateList[i]); + } + _slotTimelineStateList.length = 0; + name = null; + } + + /** @private */ + dragonBones_internal function fadeIn(aniData:AnimationData, playTimes:Number, timeScale:Number, fadeTotalTime:Number):void + { + animationData = aniData; + + name = animationData.name; + _totalTime = animationData.duration; + autoTween = aniData.autoTween; + setTimeScale(timeScale); + setPlayTimes(playTimes); + + //reset + _isComplete = false; + _currentFrameIndex = -1; + _currentPlayTimes = -1; + if(Math.round(_totalTime * animationData.frameRate * 0.001) < 2) + { + _currentTime = _totalTime; + } + else + { + _currentTime = -1; + } + + _fadeTotalTime = fadeTotalTime * _timeScale; + _fading = _fadeTotalTime>0; + //default + _isPlaying = true; + + _listenCompleteEvent = _armature.hasEventListener(AnimationEvent.COMPLETE); + + if(this._armature.enableCache && animationCache && _fading && _boneTimelineStateList) + { + updateTransformTimeline(progress); + } + + _time = 0; + _progress = 0; + + updateTimelineStateList(); + hideBones(); + return; + } + + /** + * @private + * Update timeline state based on mixing transforms and clip. + */ + dragonBones_internal function updateTimelineStateList():void + { + resetTimelineStateList(); + var timelineName:String; + for each(var boneTimeline:TransformTimeline in animationData.timelineList) + { + timelineName = boneTimeline.name; + var bone:FastBone = _armature.getBone(timelineName); + if(bone) + { + var boneTimelineState:FastBoneTimelineState = FastBoneTimelineState.borrowObject(); + boneTimelineState.fadeIn(bone, this, boneTimeline); + _boneTimelineStateList.push(boneTimelineState); + } + } + + for each(var slotTimeline:SlotTimeline in animationData.slotTimelineList) + { + timelineName = slotTimeline.name; + var slot:FastSlot = _armature.getSlot(timelineName); + if(slot && slot.displayList.length > 0) + { + var slotTimelineState:FastSlotTimelineState = FastSlotTimelineState.borrowObject(); + slotTimelineState.fadeIn(slot, this, slotTimeline); + _slotTimelineStateList.push(slotTimelineState); + } + } + } + + /** @private */ + dragonBones_internal function advanceTime(passedTime:Number):void + { + passedTime *= _timeScale; + if(_fading) + { + //计算progress + _time += passedTime; + _progress = _time / _fadeTotalTime; + if(progress >= 1) + { + _progress = 0; + _time = 0; + _fading = false; + } + } + + if(_fading) + { + //update boneTimelie + for each(var timeline:FastBoneTimelineState in _boneTimelineStateList) + { + timeline.updateFade(progress); + } + //update slotTimelie + for each(var slotTimeline:FastSlotTimelineState in _slotTimelineStateList) + { + slotTimeline.updateFade(progress); + } + } + else + { + advanceTimelinesTime(passedTime); + } + } + + private function advanceTimelinesTime(passedTime:Number):void + { + _time += passedTime; + + //计算是否已经播放完成isThisComplete + + var loopCompleteFlg:Boolean = false; + var completeFlg:Boolean = false; + var isThisComplete:Boolean = false; + var currentPlayTimes:int = 0; + var currentTime:int = _time * 1000; + if( _playTimes == 0 || //无限循环 + currentTime < _playTimes * _totalTime) //没有播放完毕 + { + isThisComplete = false; + + _progress = currentTime / _totalTime; + currentPlayTimes = Math.ceil(progress) || 1; + _progress -= Math.floor(progress); + currentTime %= _totalTime; + } + else + { + currentPlayTimes = _playTimes; + currentTime = _totalTime; + isThisComplete = true; + _progress = 1; + } + + _isComplete = isThisComplete; + + if(this.isUseCache()) + { + animationCache.update(progress); + } + else + { + updateTransformTimeline(progress); + } + + //update main timeline + if(_currentTime != currentTime) + { + if(_currentPlayTimes != currentPlayTimes) //check loop complete + { + if(_currentPlayTimes > 0 && currentPlayTimes > 1) + { + loopCompleteFlg = true; + } + _currentPlayTimes = currentPlayTimes; + } + if (_isComplete) + { + completeFlg = true; + } + _lastTime = _currentTime; + _currentTime = currentTime; + updateMainTimeline(isThisComplete); + } + + //抛事件 + var event:AnimationEvent; + if(completeFlg) + { + if (_armature.hasEventListener(AnimationEvent.COMPLETE)) + { + event = new AnimationEvent(AnimationEvent.COMPLETE); + event.animationState = this; + _armature.addEvent(event); + } + } + else if(loopCompleteFlg) + { + if (_armature.hasEventListener(AnimationEvent.LOOP_COMPLETE)) + { + event = new AnimationEvent(AnimationEvent.LOOP_COMPLETE); + event.animationState = this; + _armature.addEvent(event); + } + + } + } + + private function updateTransformTimeline(progress:Number):void + { + var i:int = _boneTimelineStateList.length; + var boneTimeline:FastBoneTimelineState; + var slotTimeline:FastSlotTimelineState; + + if(_isComplete) // 性能优化 + { + //update boneTimelie + while(i--) + { + boneTimeline = _boneTimelineStateList[i]; + boneTimeline.update(progress); + _isComplete = boneTimeline._isComplete && _isComplete; + } + + i = _slotTimelineStateList.length; + + //update slotTimelie + while(i--) + { + slotTimeline = _slotTimelineStateList[i]; + slotTimeline.update(progress); + _isComplete = slotTimeline._isComplete && _isComplete; + } + } + else + { + //update boneTimelie + while(i--) + { + boneTimeline = _boneTimelineStateList[i]; + boneTimeline.update(progress); + } + + i = _slotTimelineStateList.length; + + //update slotTimelie + while(i--) + { + slotTimeline = _slotTimelineStateList[i]; + slotTimeline.update(progress); + } + } + } + + private function updateMainTimeline(isThisComplete:Boolean):void + { + var frameList:Vector. = animationData.frameList; + if(frameList.length > 0) + { + var prevFrame:Frame; + var currentFrame:Frame; + for (var i:int = 0, l:int = animationData.frameList.length; i < l; ++i) + { + if(_currentFrameIndex < 0) + { + _currentFrameIndex = 0; + } + else if(_currentTime < _currentFramePosition || _currentTime >= _currentFramePosition + _currentFrameDuration || _currentTime < _lastTime) + { + _lastTime = _currentTime; + _currentFrameIndex ++; + if(_currentFrameIndex >= frameList.length) + { + if(isThisComplete) + { + _currentFrameIndex --; + break; + } + else + { + _currentFrameIndex = 0; + } + } + } + else + { + break; + } + currentFrame = frameList[_currentFrameIndex]; + + if(prevFrame) + { + _armature.arriveAtFrame(prevFrame, this); + } + + _currentFrameDuration = currentFrame.duration; + _currentFramePosition = currentFrame.position; + prevFrame = currentFrame; + } + + if(currentFrame) + { + _armature.arriveAtFrame(currentFrame, this); + } + } + } + + private function hideBones():void + { + for each(var timelineName:String in animationData.hideTimelineNameMap) + { + + var slot:FastSlot = _armature.getSlot(timelineName); + if(slot) + { + slot.hideSlots(); + } + } + } + + public function setTimeScale(value:Number):FastAnimationState + { + if(isNaN(value) || value == Infinity) + { + value = 1; + } + _timeScale = value; + return this; + } + + public function setPlayTimes(value:int):FastAnimationState + { + //如果动画只有一帧 播放一次就可以 + if(Math.round(_totalTime * 0.001 * animationData.frameRate) < 2) + { + _playTimes = 1; + } + else + { + _playTimes = value; + } + return this; + } + + /** + * playTimes Play times(0:loop forever, 1~+∞:play times, -1~-∞:will fade animation after play complete). + */ + public function get playTimes():int + { + return _playTimes; + } + + /** + * Current animation played times + */ + public function get currentPlayTimes():int + { + return _currentPlayTimes < 0 ? 0 : _currentPlayTimes; + } + + /** + * Is animation complete. + */ + public function get isComplete():Boolean + { + return _isComplete; + } + + /** + * Is animation playing. + */ + public function get isPlaying():Boolean + { + return (_isPlaying && !_isComplete); + } + + /** + * The length of the animation clip in seconds. + */ + public function get totalTime():Number + { + return _totalTime * 0.001; + } + + /** + * The current time of the animation. + */ + public function get currentTime():Number + { + return _currentTime < 0 ? 0 : _currentTime * 0.001; + } + + + public function isUseCache():Boolean + { + return _armature.enableCache && animationCache && !_fading; + } + + public function get progress():Number + { + return _progress; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/fast/animation/FastBoneTimelineState.as b/srclib/dragonBones/fast/animation/FastBoneTimelineState.as new file mode 100644 index 00000000..a468b95a --- /dev/null +++ b/srclib/dragonBones/fast/animation/FastBoneTimelineState.as @@ -0,0 +1,444 @@ +package dragonBones.fast.animation { + + import dragonBones.core.dragonBones_internal; + import dragonBones.fast.FastBone; + import dragonBones.objects.CurveData; + import dragonBones.objects.DBTransform; + import dragonBones.objects.Frame; + import dragonBones.objects.TransformFrame; + import dragonBones.objects.TransformTimeline; + import dragonBones.utils.MathUtil; + import dragonBones.utils.TransformUtil; + + import flash.geom.Point; + + use namespace dragonBones_internal; + + public class FastBoneTimelineState + { + private static var _pool:Vector. = new Vector.; + + /** @private */ + dragonBones_internal static function borrowObject():FastBoneTimelineState + { + if(_pool.length == 0) + { + return new FastBoneTimelineState(); + } + return _pool.pop(); + } + + /** @private */ + dragonBones_internal static function returnObject(timeline:FastBoneTimelineState):void + { + if(_pool.indexOf(timeline) < 0) + { + _pool[_pool.length] = timeline; + } + timeline.clear(); + } + + /** @private */ + dragonBones_internal static function clear():void + { + var i:int = _pool.length; + while(i --) + { + _pool[i].clear(); + } + _pool.length = 0; + } + + public var name:String; + private var _totalTime:int; //duration + private var _currentTime:int; + private var _lastTime:int; + private var _currentFrameIndex:int; + private var _currentFramePosition:int; + private var _currentFrameDuration:int; + + private var _bone:FastBone; + private var _timelineData:TransformTimeline; + private var _durationTransform:DBTransform; + + private var _tweenTransform:Boolean; + private var _tweenEasing:Number; + private var _tweenCurve:CurveData; + + private var _updateMode:int; + private var _transformToFadein:DBTransform; + /** @private */ + dragonBones_internal var _animationState:FastAnimationState; + /** @private */ + dragonBones_internal var _isComplete:Boolean; + /** @private */ + dragonBones_internal var _transform:DBTransform; + + + public function FastBoneTimelineState() + { + _transform = new DBTransform(); + _durationTransform = new DBTransform(); + _transformToFadein = new DBTransform(); + } + + private function clear():void + { + if(_bone) + { + _bone._timelineState = null; + _bone = null; + } + _animationState = null; + _timelineData = null; + } + + /** @private */ + dragonBones_internal function fadeIn(bone:FastBone, animationState:FastAnimationState, timelineData:TransformTimeline):void + { + _bone = bone; + _animationState = animationState; + _timelineData = timelineData; + + name = timelineData.name; + + _totalTime = _timelineData.duration; + + _isComplete = false; + + _tweenTransform = false; + _currentFrameIndex = -1; + _currentTime = -1; + _tweenEasing = NaN; + + switch(_timelineData.frameList.length) + { + case 0: + _updateMode = 0; + break; + + case 1: + _updateMode = 1; + break; + + default: + _updateMode = -1; + break; + } + + + if(animationState._fadeTotalTime>0) + { + var pivotToFadein:Point; + if(_bone._timelineState) + { + _transformToFadein.copy(_bone._timelineState._transform); + + } + else + { + _transformToFadein = new DBTransform(); +// _pivotToFadein = new Point(); + } + var firstFrame:TransformFrame = _timelineData.frameList[0] as TransformFrame; + _durationTransform.copy(firstFrame.transform); + _durationTransform.minus(this._transformToFadein); +// _durationPivot.x = firstFrame.pivot.x - _pivotToFadein.x; +// _durationPivot.y = firstFrame.pivot.y - _pivotToFadein.y; + } + + _bone._timelineState = this; + } + + /** @private */ + dragonBones_internal function updateFade(progress:Number):void + { + _transform.x = _transformToFadein.x + _durationTransform.x * progress; + _transform.y = _transformToFadein.y + _durationTransform.y * progress; + _transform.scaleX = _transformToFadein.scaleX * (1 + (_durationTransform.scaleX -1 ) * progress); + _transform.scaleY = _transformToFadein.scaleX * (1 + (_durationTransform.scaleY -1 ) * progress); + _transform.rotation = _transformToFadein.rotation + _durationTransform.rotation * progress; + + _bone.invalidUpdate(); + } + + /** @private */ + dragonBones_internal function update(progress:Number):void + { + if(_updateMode == 1) + { + _updateMode = 0; + updateSingleFrame(); + + } + else if(_updateMode == -1) + { + updateMultipleFrame(progress); + } + } + + private function updateSingleFrame():void + { + var currentFrame:TransformFrame = _timelineData.frameList[0] as TransformFrame; + _bone.arriveAtFrame(currentFrame, _animationState); + _isComplete = true; + _tweenEasing = NaN; + _tweenTransform = false; + + _transform.copy(currentFrame.transform); + + _bone.invalidUpdate(); + } + + private function updateMultipleFrame(progress:Number):void + { + var currentPlayTimes:int = 0; + progress /= _timelineData.scale; + progress += _timelineData.offset; + + var currentTime:int = _totalTime * progress; + var playTimes:int = _animationState.playTimes; + if(playTimes == 0) + { + _isComplete = false; + currentPlayTimes = Math.ceil(Math.abs(currentTime) / _totalTime) || 1; + currentTime -= int(currentTime / _totalTime) * _totalTime; + + if(currentTime < 0) + { + currentTime += _totalTime; + } + } + else + { + var totalTimes:int = playTimes * _totalTime; + if(currentTime >= totalTimes) + { + currentTime = totalTimes; + _isComplete = true; + } + else if(currentTime <= -totalTimes) + { + currentTime = -totalTimes; + _isComplete = true; + } + else + { + _isComplete = false; + } + + if(currentTime < 0) + { + currentTime += totalTimes; + } + + currentPlayTimes = Math.ceil(currentTime / _totalTime) || 1; + if(_isComplete) + { + currentTime = _totalTime; + } + else + { + currentTime -= int(currentTime / _totalTime) * _totalTime; + } + } + + if(_currentTime != currentTime) + { + _lastTime = _currentTime; + _currentTime = currentTime; + + var frameList:Vector. = _timelineData.frameList; + var prevFrame:TransformFrame; + var currentFrame:TransformFrame; + + for (var i:int = 0, l:int = _timelineData.frameList.length; i < l; ++i) + { + if(_currentFrameIndex < 0) + { + _currentFrameIndex = 0; + } + else if(_currentTime < _currentFramePosition || _currentTime >= _currentFramePosition + _currentFrameDuration || _currentTime < _lastTime) + { + _currentFrameIndex ++; + _lastTime = _currentTime; + if(_currentFrameIndex >= frameList.length) + { + if(_isComplete) + { + _currentFrameIndex --; + break; + } + else + { + _currentFrameIndex = 0; + } + } + } + else + { + break; + } + currentFrame = frameList[_currentFrameIndex] as TransformFrame; + + if(prevFrame) + { + _bone.arriveAtFrame(prevFrame, _animationState); + } + + _currentFrameDuration = currentFrame.duration; + _currentFramePosition = currentFrame.position; + prevFrame = currentFrame; + } + + if(currentFrame) + { + _bone.arriveAtFrame(currentFrame, _animationState); + updateToNextFrame(currentPlayTimes); + } + + if(_tweenTransform) + { + updateTween(); + } + + } + + } + + private function updateToNextFrame(currentPlayTimes:int):void + { + var nextFrameIndex:int = _currentFrameIndex + 1; + if(nextFrameIndex >= _timelineData.frameList.length) + { + nextFrameIndex = 0; + } + var currentFrame:TransformFrame = _timelineData.frameList[_currentFrameIndex] as TransformFrame; + var nextFrame:TransformFrame = _timelineData.frameList[nextFrameIndex] as TransformFrame; + var tweenEnabled:Boolean = false; + if(nextFrameIndex == 0 &&( _animationState.playTimes && + _animationState.currentPlayTimes >= _animationState.playTimes && + ((_currentFramePosition + _currentFrameDuration) / _totalTime + currentPlayTimes - _timelineData.offset)* _timelineData.scale > 0.999999 + )) + { + _tweenEasing = NaN; + tweenEnabled = false; + } + else if(_animationState.autoTween) + { + _tweenEasing = _animationState.animationData.tweenEasing; + if(isNaN(_tweenEasing)) + { + _tweenEasing = currentFrame.tweenEasing; + _tweenCurve = currentFrame.curve; + if(isNaN(_tweenEasing)) //frame no tween + { + tweenEnabled = false; + } + else + { + if(_tweenEasing == 10) + { + _tweenEasing = 0; + } + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + else //animationData overwrite tween + { + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + else + { + _tweenEasing = currentFrame.tweenEasing; + _tweenCurve = currentFrame.curve; + if(isNaN(_tweenEasing) || _tweenEasing == 10) //frame no tween + { + _tweenEasing = NaN; + tweenEnabled = false; + } + else + { + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + + if(tweenEnabled) + { + //transform + _durationTransform.x = nextFrame.transform.x - currentFrame.transform.x; + _durationTransform.y = nextFrame.transform.y - currentFrame.transform.y; + _durationTransform.skewX = nextFrame.transform.skewX - currentFrame.transform.skewX; + _durationTransform.skewY = nextFrame.transform.skewY - currentFrame.transform.skewY; + + _durationTransform.scaleX = nextFrame.transform.scaleX - currentFrame.transform.scaleX + nextFrame.scaleOffset.x; + _durationTransform.scaleY = nextFrame.transform.scaleY - currentFrame.transform.scaleY + nextFrame.scaleOffset.y; + _durationTransform.normalizeRotation(); + if(nextFrameIndex == 0) + { + _durationTransform.skewX = TransformUtil.formatRadian(_durationTransform.skewX); + _durationTransform.skewY = TransformUtil.formatRadian(_durationTransform.skewY); + } + + if( + _durationTransform.x || + _durationTransform.y || + _durationTransform.skewX || + _durationTransform.skewY || + _durationTransform.scaleX != 1 || + _durationTransform.scaleY != 1 //|| + ) + { + _tweenTransform = true; + } + else + { + _tweenTransform = false; + } + + } + else + { + _tweenTransform = false; + } + + if(!_tweenTransform) + { + _transform.copy(currentFrame.transform); + + _bone.invalidUpdate(); + } + } + + private function updateTween():void + { + var progress:Number = (_currentTime - _currentFramePosition) / _currentFrameDuration; + if (_tweenCurve) + { + progress = _tweenCurve.getValueByProgress(progress); + } + if(_tweenEasing) + { + progress = MathUtil.getEaseValue(progress, _tweenEasing); + } + + var currentFrame:TransformFrame = _timelineData.frameList[_currentFrameIndex] as TransformFrame; + + var currentTransform:DBTransform = currentFrame.transform; + var currentPivot:Point = currentFrame.pivot; + //normal blending + _transform.x = currentTransform.x + _durationTransform.x * progress; + _transform.y = currentTransform.y + _durationTransform.y * progress; + _transform.skewX = currentTransform.skewX + _durationTransform.skewX * progress; + _transform.skewY = currentTransform.skewY + _durationTransform.skewY * progress; + _transform.scaleX = currentTransform.scaleX + _durationTransform.scaleX * progress; + _transform.scaleY = currentTransform.scaleY + _durationTransform.scaleY * progress; + + _bone.invalidUpdate(); + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/fast/animation/FastSlotTimelineState.as b/srclib/dragonBones/fast/animation/FastSlotTimelineState.as new file mode 100644 index 00000000..88659cda --- /dev/null +++ b/srclib/dragonBones/fast/animation/FastSlotTimelineState.as @@ -0,0 +1,511 @@ +package dragonBones.fast.animation { + + import dragonBones.core.dragonBones_internal; + import dragonBones.fast.FastArmature; + import dragonBones.fast.FastSlot; + import dragonBones.objects.CurveData; + import dragonBones.objects.Frame; + import dragonBones.objects.SlotFrame; + import dragonBones.objects.SlotTimeline; + import dragonBones.utils.ColorTransformUtil; + import dragonBones.utils.MathUtil; + + import flash.geom.ColorTransform; + + use namespace dragonBones_internal; + + /** @private */ + public final class FastSlotTimelineState + { + private static const HALF_PI:Number = Math.PI * 0.5; + private static const DOUBLE_PI:Number = Math.PI * 2; + + private static var _pool:Vector. = new Vector.; + + /** @private */ + dragonBones_internal static function borrowObject():FastSlotTimelineState + { + if(_pool.length == 0) + { + return new FastSlotTimelineState(); + } + return _pool.pop(); + } + + /** @private */ + dragonBones_internal static function returnObject(timeline:FastSlotTimelineState):void + { + if(_pool.indexOf(timeline) < 0) + { + _pool[_pool.length] = timeline; + } + + timeline.clear(); + } + + /** @private */ + dragonBones_internal static function clear():void + { + var i:int = _pool.length; + while(i --) + { + _pool[i].clear(); + } + _pool.length = 0; + } + + + + public var name:String; + + /** @private */ + dragonBones_internal var _weight:Number; + + //TO DO 干什么用的 + /** @private */ + dragonBones_internal var _blendEnabled:Boolean; + + /** @private */ + dragonBones_internal var _isComplete:Boolean; + + /** @private */ + dragonBones_internal var _animationState:FastAnimationState; + + private var _totalTime:int; //duration + + private var _currentTime:int; + private var _currentFrameIndex:int; + private var _currentFramePosition:int; + private var _currentFrameDuration:int; + + private var _tweenEasing:Number; + private var _tweenCurve:CurveData; + private var _tweenColor:Boolean; + private var _colorChanged:Boolean; + + //-1: frameLength>1, 0:frameLength==0, 1:frameLength==1 + private var _updateMode:int; + + private var _armature:FastArmature; + private var _animation:FastAnimation; + private var _slot:FastSlot; + + private var _timelineData:SlotTimeline; + private var _durationColor:ColorTransform; + + + + public function FastSlotTimelineState() + { + _durationColor = new ColorTransform(); + } + + private function clear():void + { + _slot = null; + _armature = null; + _animation = null; + _animationState = null; + _timelineData = null; + } + + //动画开始结束 + /** @private */ + dragonBones_internal function fadeIn(slot:FastSlot, animationState:FastAnimationState, timelineData:SlotTimeline):void + { + _slot = slot; + _armature = _slot.armature; + _animation = _armature.animation as FastAnimation; + _animationState = animationState; + _timelineData = timelineData; + + name = timelineData.name; + + _totalTime = _timelineData.duration; + + _isComplete = false; + _blendEnabled = false; + _tweenColor = false; + _currentFrameIndex = -1; + _currentTime = -1; + _tweenEasing = NaN; + _weight = 1; + + switch(_timelineData.frameList.length) + { + case 0: + _updateMode = 0; + break; + + case 1: + _updateMode = 1; + break; + + default: + _updateMode = -1; + break; + } + } + + //动画进行中 + + /** @private */ + dragonBones_internal function updateFade(progress:Number):void + { + } + + /** @private */ + dragonBones_internal function update(progress:Number):void + { + if(_updateMode == -1) + { + updateMultipleFrame(progress); + } + else if(_updateMode == 1) + { + _updateMode = 0; + updateSingleFrame(); + } + } + + private function updateMultipleFrame(progress:Number):void + { + var currentPlayTimes:int = 0; + progress /= _timelineData.scale; + progress += _timelineData.offset; + + var currentTime:int = _totalTime * progress; + var playTimes:int = _animationState.playTimes; + if(playTimes == 0) + { + _isComplete = false; + currentPlayTimes = Math.ceil(Math.abs(currentTime) / _totalTime) || 1; + currentTime -= int(currentTime / _totalTime) * _totalTime; + + if(currentTime < 0) + { + currentTime += _totalTime; + } + } + else + { + var totalTimes:int = playTimes * _totalTime; + if(currentTime >= totalTimes) + { + currentTime = totalTimes; + _isComplete = true; + } + else if(currentTime <= -totalTimes) + { + currentTime = -totalTimes; + _isComplete = true; + } + else + { + _isComplete = false; + } + + if(currentTime < 0) + { + currentTime += totalTimes; + } + + currentPlayTimes = Math.ceil(currentTime / _totalTime) || 1; + if(_isComplete) + { + currentTime = _totalTime; + } + else + { + currentTime -= int(currentTime / _totalTime) * _totalTime; + } + } + + if(_currentTime != currentTime) + { + _currentTime = currentTime; + + var frameList:Vector. = _timelineData.frameList; + var prevFrame:SlotFrame; + var currentFrame:SlotFrame; + + for (var i:int = 0, l:int = _timelineData.frameList.length; i < l; ++i) + { + if(_currentFrameIndex < 0) + { + _currentFrameIndex = 0; + } + else if(_currentTime < _currentFramePosition || _currentTime >= _currentFramePosition + _currentFrameDuration) + { + _currentFrameIndex ++; + if(_currentFrameIndex >= frameList.length) + { + if(_isComplete) + { + _currentFrameIndex --; + break; + } + else + { + _currentFrameIndex = 0; + } + } + } + else + { + break; + } + currentFrame = frameList[_currentFrameIndex] as SlotFrame; + + if(prevFrame) + { + _slot.arriveAtFrame(prevFrame, _animationState); + } + + _currentFrameDuration = currentFrame.duration; + _currentFramePosition = currentFrame.position; + prevFrame = currentFrame; + } + + if(currentFrame) + { + _slot.arriveAtFrame(currentFrame, _animationState); + + _blendEnabled = currentFrame.displayIndex >= 0; + if(_blendEnabled) + { + updateToNextFrame(currentPlayTimes); + } + else + { + _tweenEasing = NaN; + _tweenColor = false; + } + } + + if(_blendEnabled) + { + updateTween(); + } + } + } + + private function updateToNextFrame(currentPlayTimes:int):void + { + var nextFrameIndex:int = _currentFrameIndex + 1; + if(nextFrameIndex >= _timelineData.frameList.length) + { + nextFrameIndex = 0; + } + var currentFrame:SlotFrame = _timelineData.frameList[_currentFrameIndex] as SlotFrame; + var nextFrame:SlotFrame = _timelineData.frameList[nextFrameIndex] as SlotFrame; + var tweenEnabled:Boolean = false; + if(nextFrameIndex == 0 && + ( + _animationState.playTimes && + _animationState.currentPlayTimes >= _animationState.playTimes && + ((_currentFramePosition + _currentFrameDuration) / _totalTime + currentPlayTimes - _timelineData.offset) * _timelineData.scale > 0.999999 + ) + ) + { + _tweenEasing = NaN; + tweenEnabled = false; + } + else if(currentFrame.displayIndex < 0 || nextFrame.displayIndex < 0) + { + _tweenEasing = NaN; + tweenEnabled = false; + } + else if(_animationState.autoTween) + { + _tweenEasing = _animationState.animationData.tweenEasing; + if(isNaN(_tweenEasing)) + { + _tweenEasing = currentFrame.tweenEasing; + _tweenCurve = currentFrame.curve; + if(isNaN(_tweenEasing)) //frame no tween + { + tweenEnabled = false; + } + else + { + if(_tweenEasing == 10) + { + _tweenEasing = 0; + } + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + else //animationData overwrite tween + { + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + else + { + _tweenEasing = currentFrame.tweenEasing; + _tweenCurve = currentFrame.curve; + if(isNaN(_tweenEasing) || _tweenEasing == 10) //frame no tween + { + _tweenEasing = NaN; + tweenEnabled = false; + } + else + { + //_tweenEasing [-1, 0) 0 (0, 1] (1, 2] + tweenEnabled = true; + } + } + + if(tweenEnabled) + { + if(currentFrame.color || nextFrame.color) + { + ColorTransformUtil.minus(nextFrame.color || ColorTransformUtil.originalColor, currentFrame.color ||ColorTransformUtil.originalColor, _durationColor); + _tweenColor = _durationColor.alphaOffset || + _durationColor.redOffset || + _durationColor.greenOffset || + _durationColor.blueOffset || + _durationColor.alphaMultiplier || + _durationColor.redMultiplier || + _durationColor.greenMultiplier || + _durationColor.blueMultiplier; + } + else + { + _tweenColor = false; + } + } + else + { + _tweenColor = false; + } + + if(!_tweenColor) + { + var targetColor:ColorTransform; + var colorChanged:Boolean; + + if(currentFrame.colorChanged) + { + targetColor = currentFrame.color; + colorChanged = true; + } + else + { + targetColor = ColorTransformUtil.originalColor; + colorChanged = false; + } + if ((_slot._isColorChanged || colorChanged)) + { + if( !ColorTransformUtil.isEqual(_slot._colorTransform, targetColor)) + { + _slot.updateDisplayColor( + targetColor.alphaOffset, + targetColor.redOffset, + targetColor.greenOffset, + targetColor.blueOffset, + targetColor.alphaMultiplier, + targetColor.redMultiplier, + targetColor.greenMultiplier, + targetColor.blueMultiplier, + colorChanged + ); + } + } + } + } + + private function updateTween():void + { + var currentFrame:SlotFrame = _timelineData.frameList[_currentFrameIndex] as SlotFrame; + + if(_tweenColor) + { + var progress:Number = (_currentTime - _currentFramePosition) / _currentFrameDuration; + if (_tweenCurve != null) + { + progress = _tweenCurve.getValueByProgress(progress); + } + if(_tweenEasing) + { + progress = MathUtil.getEaseValue(progress, _tweenEasing); + } + if(currentFrame.color) + { + _slot.updateDisplayColor( + currentFrame.color.alphaOffset + _durationColor.alphaOffset * progress, + currentFrame.color.redOffset + _durationColor.redOffset * progress, + currentFrame.color.greenOffset + _durationColor.greenOffset * progress, + currentFrame.color.blueOffset + _durationColor.blueOffset * progress, + currentFrame.color.alphaMultiplier + _durationColor.alphaMultiplier * progress, + currentFrame.color.redMultiplier + _durationColor.redMultiplier * progress, + currentFrame.color.greenMultiplier + _durationColor.greenMultiplier * progress, + currentFrame.color.blueMultiplier + _durationColor.blueMultiplier * progress, + true + ); + } + else + { + _slot.updateDisplayColor( + _durationColor.alphaOffset * progress, + _durationColor.redOffset * progress, + _durationColor.greenOffset * progress, + _durationColor.blueOffset * progress, + _durationColor.alphaMultiplier * progress + 1, + _durationColor.redMultiplier * progress + 1, + _durationColor.greenMultiplier * progress + 1, + _durationColor.blueMultiplier * progress + 1, + true + ); + } + } + } + + private function updateSingleFrame():void + { + var currentFrame:SlotFrame = _timelineData.frameList[0] as SlotFrame; + _slot.arriveAtFrame(currentFrame, _animationState); + _isComplete = true; + _tweenEasing = NaN; + _tweenColor = false; + + _blendEnabled = currentFrame.displayIndex >= 0; + if(_blendEnabled) + { + var targetColor:ColorTransform; + var colorChanged:Boolean; + if(currentFrame.colorChanged) + { + targetColor = currentFrame.color; + colorChanged = true; + } + else + { + targetColor = ColorTransformUtil.originalColor; + colorChanged = false; + } + if ((_slot._isColorChanged || colorChanged)) + { + if( !ColorTransformUtil.isEqual(_slot._colorTransform, targetColor)) + { + _slot.updateDisplayColor( + targetColor.alphaOffset, + targetColor.redOffset, + targetColor.greenOffset, + targetColor.blueOffset, + targetColor.alphaMultiplier, + targetColor.redMultiplier, + targetColor.greenMultiplier, + targetColor.blueMultiplier, + colorChanged + ); + } + } + } + } + + } +} diff --git a/srclib/dragonBones/objects/AnimationData.as b/srclib/dragonBones/objects/AnimationData.as new file mode 100644 index 00000000..af9f027d --- /dev/null +++ b/srclib/dragonBones/objects/AnimationData.as @@ -0,0 +1,128 @@ +package dragonBones.objects +{ + final public class AnimationData extends Timeline + { + public var name:String; + public var frameRate:uint; + public var fadeTime:Number; + public var playTimes:int; + //use frame tweenEase, NaN + //overwrite frame tweenEase, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + public var tweenEasing:Number; + public var autoTween:Boolean; + public var lastFrameDuration:int; + + public var hideTimelineNameMap:Vector.; + + private var _timelineList:Vector.; + public function get timelineList():Vector. + { + return _timelineList; + } + + private var _slotTimelineList:Vector.; + public function get slotTimelineList():Vector. + { + return _slotTimelineList; + } + + public function AnimationData() + { + super(); + fadeTime = 0; + playTimes = 0; + autoTween = true; + tweenEasing = NaN; + hideTimelineNameMap = new Vector.; + hideTimelineNameMap.fixed = true; + + _timelineList = new Vector.; + _timelineList.fixed = true; + _slotTimelineList = new Vector.; + _slotTimelineList.fixed = true; + } + + override public function dispose():void + { + super.dispose(); + + hideTimelineNameMap.fixed = false; + hideTimelineNameMap.length = 0; + hideTimelineNameMap = null; + + _timelineList.fixed = false; + for each(var timeline:TransformTimeline in _timelineList) + { + timeline.dispose(); + } + _timelineList.fixed = false; + _timelineList.length = 0; + _timelineList = null; + + _slotTimelineList.fixed = false; + for each(var slotTimeline:SlotTimeline in _slotTimelineList) + { + slotTimeline.dispose(); + } + _slotTimelineList.fixed = false; + _slotTimelineList.length = 0; + _slotTimelineList = null; + } + + public function getTimeline(timelineName:String):TransformTimeline + { + var i:int = _timelineList.length; + while(i --) + { + if(_timelineList[i].name == timelineName) + { + return _timelineList[i]; + } + } + return null; + } + + public function addTimeline(timeline:TransformTimeline):void + { + if(!timeline) + { + throw new ArgumentError(); + } + + if(_timelineList.indexOf(timeline) < 0) + { + _timelineList.fixed = false; + _timelineList[_timelineList.length] = timeline; + _timelineList.fixed = true; + } + } + + public function getSlotTimeline(timelineName:String):SlotTimeline + { + var i:int = _slotTimelineList.length; + while(i --) + { + if(_slotTimelineList[i].name == timelineName) + { + return _slotTimelineList[i]; + } + } + return null; + } + + public function addSlotTimeline(timeline:SlotTimeline):void + { + if(!timeline) + { + throw new ArgumentError(); + } + + if(_slotTimelineList.indexOf(timeline) < 0) + { + _slotTimelineList.fixed = false; + _slotTimelineList[_slotTimelineList.length] = timeline; + _slotTimelineList.fixed = true; + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/ArmatureData.as b/srclib/dragonBones/objects/ArmatureData.as new file mode 100644 index 00000000..91ee93b3 --- /dev/null +++ b/srclib/dragonBones/objects/ArmatureData.as @@ -0,0 +1,324 @@ +package dragonBones.objects +{ + /** @private */ + final public class ArmatureData + { + public var name:String; + + private var _boneDataList:Vector.; + private var _skinDataList:Vector.; + private var _slotDataList:Vector.; + private var _animationDataList:Vector.; + + public function ArmatureData() + { + _boneDataList = new Vector.(0, true); + _skinDataList = new Vector.(0, true); + _slotDataList = new Vector.(0, true); + _animationDataList = new Vector.(0, true); + + //_areaDataList = new Vector.(0, true); + } + + public function setSkinData(skinName:String):void + { + for (var i:int = 0, len:int = _slotDataList.length; i < len; i++) + { + _slotDataList[i].dispose(); + } + var skinData:SkinData; + if(!skinName && _skinDataList.length > 0) + { + skinData = _skinDataList[0]; + } + else + { + for (i = 0, len = _skinDataList.length; i < len; i++) + { + if (_skinDataList[i].name == skinName) + { + skinData = _skinDataList[i]; + break; + } + } + } + + if (skinData) + { + var slotData:SlotData; + for (i = 0, len = skinData.slotDataList.length; i < len; i++) + { + slotData = getSlotData(skinData.slotDataList[i].name); + if (slotData) + { + for (var j:int = 0, jLen:int = skinData.slotDataList[i].displayDataList.length; j < jLen; j++) + { + slotData.addDisplayData(skinData.slotDataList[i].displayDataList[j]); + } + } + } + } + } + + public function dispose():void + { + var i:int = _boneDataList.length; + while(i --) + { + _boneDataList[i].dispose(); + } + i = _skinDataList.length; + while(i --) + { + _skinDataList[i].dispose(); + } + i = _slotDataList.length; + while(i --) + { + _slotDataList[i].dispose(); + } + i = _animationDataList.length; + while(i --) + { + _animationDataList[i].dispose(); + } + + _boneDataList.fixed = false; + _boneDataList.length = 0; + _skinDataList.fixed = false; + _skinDataList.length = 0; + _slotDataList.fixed = false; + _slotDataList.length = 0; + _animationDataList.fixed = false; + _animationDataList.length = 0; + //_animationsCached。clear(); + _boneDataList = null; + _skinDataList = null; + _slotDataList = null; + _animationDataList = null; + } + + public function getBoneData(boneName:String):BoneData + { + var i:int = _boneDataList.length; + while(i --) + { + if(_boneDataList[i].name == boneName) + { + return _boneDataList[i]; + } + } + return null; + } + + public function getSlotData(slotName:String):SlotData + { + if(!slotName && _slotDataList.length > 0) + { + return _slotDataList[0]; + } + var i:int = _slotDataList.length; + while(i --) + { + if(_slotDataList[i].name == slotName) + { + return _slotDataList[i]; + } + } + + return null; + } + + public function getSkinData(skinName:String):SkinData + { + if(!skinName && _skinDataList.length > 0) + { + return _skinDataList[0]; + } + var i:int = _skinDataList.length; + while(i --) + { + if(_skinDataList[i].name == skinName) + { + return _skinDataList[i]; + } + } + + return null; + } + + public function getAnimationData(animationName:String):AnimationData + { + var i:int = _animationDataList.length; + while(i --) + { + if(_animationDataList[i].name == animationName) + { + return _animationDataList[i]; + } + } + return null; + } + + public function addBoneData(boneData:BoneData):void + { + if(!boneData) + { + throw new ArgumentError(); + } + + if (_boneDataList.indexOf(boneData) < 0) + { + _boneDataList.fixed = false; + _boneDataList[_boneDataList.length] = boneData; + _boneDataList.fixed = true; + } + else + { + throw new ArgumentError(); + } + } + + public function addSlotData(slotData:SlotData):void + { + if(!slotData) + { + throw new ArgumentError(); + } + + if(_slotDataList.indexOf(slotData) < 0) + { + _slotDataList.fixed = false; + _slotDataList[_slotDataList.length] = slotData; + _slotDataList.fixed = true; + } + else + { + throw new ArgumentError(); + } + } + + public function addSkinData(skinData:SkinData):void + { + if(!skinData) + { + throw new ArgumentError(); + } + + if(_skinDataList.indexOf(skinData) < 0) + { + _skinDataList.fixed = false; + _skinDataList[_skinDataList.length] = skinData; + _skinDataList.fixed = true; + } + else + { + throw new ArgumentError(); + } + } + + public function addAnimationData(animationData:AnimationData):void + { + if(!animationData) + { + throw new ArgumentError(); + } + + if(_animationDataList.indexOf(animationData) < 0) + { + _animationDataList.fixed = false; + _animationDataList[_animationDataList.length] = animationData; + _animationDataList.fixed = true; + } + } + + public function sortBoneDataList():void + { + var i:int = _boneDataList.length; + if(i == 0) + { + return; + } + + var helpArray:Array = []; + while(i --) + { + var boneData:BoneData = _boneDataList[i]; + var level:int = 0; + var parentData:BoneData = boneData; + while(parentData) + { + level ++; + parentData = getBoneData(parentData.parent); + } + helpArray[i] = [level, boneData]; + } + + helpArray.sortOn("0", Array.NUMERIC); + + i = helpArray.length; + while(i --) + { + _boneDataList[i] = helpArray[i][1]; + } + } + + public function get boneDataList():Vector. + { + return _boneDataList; + } + public function get skinDataList():Vector. + { + return _skinDataList; + } + public function get animationDataList():Vector. + { + return _animationDataList; + } + + public function get slotDataList():Vector. + { + return _slotDataList; + } + + /* + private var _areaDataList:Vector.; + public function get areaDataList():Vector. + { + return _areaDataList; + } + + public function getAreaData(areaName:String):IAreaData + { + if(!areaName && _areaDataList.length > 0) + { + return _areaDataList[0]; + } + var i:int = _areaDataList.length; + while(i --) + { + if(_areaDataList[i]["name"] == areaName) + { + return _areaDataList[i]; + } + } + return null; + } + + public function addAreaData(areaData:IAreaData):void + { + if(!areaData) + { + throw new ArgumentError(); + } + + if(_areaDataList.indexOf(areaData) < 0) + { + _areaDataList.fixed = false; + _areaDataList[_areaDataList.length] = areaData; + _areaDataList.fixed = true; + } + } + */ + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/BoneData.as b/srclib/dragonBones/objects/BoneData.as new file mode 100644 index 00000000..58c9b958 --- /dev/null +++ b/srclib/dragonBones/objects/BoneData.as @@ -0,0 +1,85 @@ +package dragonBones.objects +{ + final public class BoneData + { + public var name:String; + public var parent:String; + public var length:Number; + + public var global:DBTransform; + public var transform:DBTransform; + + public var inheritScale:Boolean; + public var inheritRotation:Boolean; + + public function BoneData() + { + length = 0; + global = new DBTransform(); + transform = new DBTransform(); + inheritRotation = true; + inheritScale = false; + + //_areaDataList = new Vector.(0, true); + } + + public function dispose():void + { + global = null; + transform = null; + /* + if(_areaDataList) + { + for each(var areaData:IAreaData in _areaDataList) + { + areaData.dispose(); + } + _areaDataList.fixed = false; + _areaDataList.length = 0; + _areaDataList = null; + } + */ + } + + /* + private var _areaDataList:Vector.; + public function get areaDataList():Vector. + { + return _areaDataList; + } + + + public function getAreaData(areaName:String):IAreaData + { + if(!areaName && _areaDataList.length > 0) + { + return _areaDataList[0]; + } + var i:int = _areaDataList.length; + while(i --) + { + if(_areaDataList[i]["name"] == areaName) + { + return _areaDataList[i]; + } + } + return null; + } + + public function addAreaData(areaData:IAreaData):void + { + if(!areaData) + { + throw new ArgumentError(); + } + + if(_areaDataList.indexOf(areaData) < 0) + { + _areaDataList.fixed = false; + _areaDataList[_areaDataList.length] = areaData; + _areaDataList.fixed = true; + } + } + */ + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/CurveData.as b/srclib/dragonBones/objects/CurveData.as new file mode 100644 index 00000000..58acfbb5 --- /dev/null +++ b/srclib/dragonBones/objects/CurveData.as @@ -0,0 +1,93 @@ +package dragonBones.objects { + + import flash.geom.Point; + + /** + * 目前只支持两个控制点的贝塞尔曲线 + * @author CG + */ + public class CurveData + { + private static const SamplingTimes:int = 20; + private static const SamplingStep:Number = 0.05; + private var _dataChanged:Boolean = false; + + private var _pointList:Array = []; + public var sampling:Vector. = new Vector.(SamplingTimes); + + public function CurveData() + { + for(var i:int=0; i < SamplingTimes-1; i++) + { + sampling[i] = new Point(); + } + sampling.fixed = true; + } + + public function getValueByProgress(progress:Number):Number + { + if(_dataChanged) + { + refreshSampling(); + } + for (var i:int = 0; i < SamplingTimes-1; i++) + { + var point:Point = sampling[i]; + if (point.x >= progress) + { + if(i == 0) + { + return point.y * progress / point.x; + } + else + { + var prevPoint:Point = sampling[i-1]; + return prevPoint.y + (point.y - prevPoint.y) * (progress - prevPoint.x) / (point.x - prevPoint.x); + } + + } + } + return point.y + (1 - point.y) * (progress - point.x) / (1 - point.x); + } + + public function refreshSampling():void + { + for(var i:int = 0; i < SamplingTimes-1; i++) + { + bezierCurve(SamplingStep * (i+1), sampling[i]); + } + _dataChanged = false; + } + + private function bezierCurve(t:Number, outputPoint:Point):void + { + var l_t:Number = 1-t; + outputPoint.x = 3* point1.x*t*l_t*l_t + 3*point2.x*t*t*l_t + Math.pow(t,3); + outputPoint.y = 3* point1.y*t*l_t*l_t + 3*point2.y*t*t*l_t + Math.pow(t,3); + } + + public function set pointList(value:Array):void + { + _pointList = value; + _dataChanged = true; + } + + public function get pointList():Array + { + return _pointList; + } + + public function isCurve():Boolean + { + return point1.x != 0 || point1.y != 0 || point2.x != 1 || point2.y != 1; + } + public function get point1():Point + { + return pointList[0]; + } + public function get point2():Point + { + return pointList[1]; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/DBTransform.as b/srclib/dragonBones/objects/DBTransform.as new file mode 100644 index 00000000..d001fbda --- /dev/null +++ b/srclib/dragonBones/objects/DBTransform.as @@ -0,0 +1,131 @@ +package dragonBones.objects { + + import dragonBones.utils.TransformUtil; + + import flash.geom.Matrix; + + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0 + * @langversion 3.0 + * @version 2.0 + */ + public class DBTransform + { + /** + * Position on the x axis. + */ + public var x:Number; + /** + * Position on the y axis. + */ + public var y:Number; + /** + * Skew on the x axis. + */ + public var skewX:Number; + /** + * skew on the y axis. + */ + public var skewY:Number; + /** + * Scale on the x axis. + */ + public var scaleX:Number; + /** + * Scale on the y axis. + */ + public var scaleY:Number; + /** + * The rotation of that DBTransform instance. + */ + public function get rotation():Number + { + return skewX; + } + public function set rotation(value:Number):void + { + skewX = skewY = value; + } + /** + * Creat a new DBTransform instance. + */ + public function DBTransform() + { + x = 0; + y = 0; + skewX = 0; + skewY = 0; + scaleX = 1 + scaleY = 1; + } + /** + * Copy all properties from this DBTransform instance to the passed DBTransform instance. + * @param node + */ + public function copy(transform:DBTransform):void + { + x = transform.x; + y = transform.y; + skewX = transform.skewX; + skewY = transform.skewY; + scaleX = transform.scaleX; + scaleY = transform.scaleY; + } + + public function add(transform:DBTransform):void + { + x += transform.x; + y += transform.y; + skewX += transform.skewX; + skewY += transform.skewY; + scaleX *= transform.scaleX; + scaleY *= transform.scaleY; + } + + public function minus(transform:DBTransform):void + { + x -= transform.x; + y -= transform.y; + skewX -= transform.skewX; + skewY -= transform.skewY; + scaleX /= transform.scaleX; + scaleY /= transform.scaleY; + } + + public function divParent(transform:DBTransform, createNew:Boolean = false):DBTransform + { + var output:DBTransform = createNew ? new DBTransform() : this; + var parentMatrix:Matrix = new Matrix(); + + TransformUtil.transformToMatrix(transform, parentMatrix); + var xtx:Number = x - parentMatrix.tx; + var yty:Number = y - parentMatrix.ty; + var adcb:Number = parentMatrix.a * parentMatrix.d - parentMatrix.c * parentMatrix.b; + + output.x = (xtx * parentMatrix.d - yty * parentMatrix.c)/adcb; + output.y = (yty * parentMatrix.a - xtx * parentMatrix.b)/adcb; + output.scaleX = scaleX / transform.scaleX; + output.scaleY = scaleY / transform.scaleY; + output.skewX = skewX - transform.skewX; + output.skewY = skewY - transform.skewY; + return output; + } + + public function normalizeRotation():void + { + skewX = TransformUtil.normalizeRotation(skewX); + skewY = TransformUtil.normalizeRotation(skewY); + } + + /** + * Get a string representing all DBTransform property values. + * @return String All property values in a formatted string. + */ + public function toString():String + { + var string:String = "x:" + x + " y:" + y + " skewX:" + skewX + " skewY:" + skewY + " scaleX:" + scaleX + " scaleY:" + scaleY; + return string; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/DataParser.as b/srclib/dragonBones/objects/DataParser.as new file mode 100644 index 00000000..dde4ce6f --- /dev/null +++ b/srclib/dragonBones/objects/DataParser.as @@ -0,0 +1,35 @@ +package dragonBones.objects +{ + public class DataParser + { + public function DataParser() + { + } + + public static function parseData(rawData:Object):DragonBonesData + { + if(rawData is XML) + { + return XMLDataParser.parseDragonBonesData(rawData as XML); + } + else + { + return ObjectDataParser.parseDragonBonesData(rawData); + } + return null; + } + + public static function parseTextureAtlasData(textureAtlasData:Object, scale:Number = 1):Object + { + if(textureAtlasData is XML) + { + return XMLDataParser.parseTextureAtlasData(textureAtlasData as XML, scale); + } + else + { + return ObjectDataParser.parseTextureAtlasData(textureAtlasData, scale); + } + return null; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/DataSerializer.as b/srclib/dragonBones/objects/DataSerializer.as new file mode 100644 index 00000000..03b9d0b1 --- /dev/null +++ b/srclib/dragonBones/objects/DataSerializer.as @@ -0,0 +1,111 @@ +package dragonBones.objects { + + import dragonBones.utils.BytesType; + + import flash.events.EventDispatcher; + import flash.utils.ByteArray; + + /** Dispatched after a sucessful call to decompressData(). */ + [Event(name="complete", type="flash.events.Event")] + public class DataSerializer extends EventDispatcher + { + public function DataSerializer() + { + } + + /** + * Compress all data into a ByteArray for serialization. + * @param The DragonBones data. + * @param The TextureAtlas data. + * @param The ByteArray representing the map. + * @return ByteArray. A DragonBones compatible ByteArray. + */ + + static public function compressDataToByteArray(dragonBonesData:Object, textureAtlasData:Object, textureAtlasBytes:ByteArray):ByteArray + { + var outputBytes:ByteArray = new ByteArray(); + outputBytes.writeBytes(textureAtlasBytes); + + var dataBytes:ByteArray = new ByteArray(); + dataBytes.writeObject(textureAtlasData); + dataBytes.compress(); + + outputBytes.position = outputBytes.length; + outputBytes.writeBytes(dataBytes); + outputBytes.writeInt(dataBytes.length); + + dataBytes.length = 0; + dataBytes.writeObject(dragonBonesData); + dataBytes.compress(); + + outputBytes.position = outputBytes.length; + outputBytes.writeBytes(dataBytes); + outputBytes.writeInt(dataBytes.length); + + return outputBytes; + } + + /** + * Decompress a compatible DragonBones data. + * @param compressedByteArray The ByteArray to decompress. + * @return A DecompressedData instance. + */ + public static function decompressData(inputByteArray:ByteArray):DecompressedData + { + var dataType:String = BytesType.getType(inputByteArray); + switch (dataType) + { + case BytesType.SWF: + case BytesType.PNG: + case BytesType.JPG: + case BytesType.ATF: + var dragonBonesData:Object; + var textureAtlasData:Object; + var textureAtlas:Object; + try + { + var tempByteArray:ByteArray = new ByteArray(); + var bytesToDecompress:ByteArray = new ByteArray(); + bytesToDecompress.writeBytes(inputByteArray); + + //Read DragonBones Data + bytesToDecompress.position = bytesToDecompress.length - 4; + var strSize:int = bytesToDecompress.readInt(); + var position:uint = bytesToDecompress.length - 4 - strSize; + tempByteArray.writeBytes(bytesToDecompress, position, strSize); + tempByteArray.uncompress(); + dragonBonesData = tempByteArray.readObject(); + + tempByteArray.length = 0; + bytesToDecompress.length = position; + + //Read TextureAtlas Data + bytesToDecompress.position = bytesToDecompress.length - 4; + strSize = bytesToDecompress.readInt(); + position = bytesToDecompress.length - 4 - strSize; + tempByteArray.writeBytes(bytesToDecompress, position, strSize); + tempByteArray.uncompress(); + textureAtlasData = tempByteArray.readObject(); + bytesToDecompress.length = position; + } + catch (e:Error) + { + throw new Error("Data error!"); + } + + var outputDecompressedData:DecompressedData = new DecompressedData(); + outputDecompressedData.textureBytesDataType = dataType; + outputDecompressedData.dragonBonesData = dragonBonesData; + outputDecompressedData.textureAtlasData = textureAtlasData; + outputDecompressedData.textureAtlasBytes = bytesToDecompress + + return outputDecompressedData; + + default: + throw new Error("Nonsupport data!"); + } + + return null; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/DecompressedData.as b/srclib/dragonBones/objects/DecompressedData.as new file mode 100644 index 00000000..a1afc9c7 --- /dev/null +++ b/srclib/dragonBones/objects/DecompressedData.as @@ -0,0 +1,84 @@ +package dragonBones.objects { + + import flash.display.Bitmap; + import flash.display.Loader; + import flash.display.MovieClip; + import flash.display.Sprite; + import flash.events.Event; + import flash.events.EventDispatcher; + import flash.utils.ByteArray; + + /** Dispatched after a sucessful call to parseTextureAtlasBytes(). */ + [Event(name="complete", type="flash.events.Event")] + public class DecompressedData extends EventDispatcher + { + /** + * data name. + */ + public var name:String; + + public var textureBytesDataType:String; + /** + * The xml or JSON for DragonBones data. + */ + public var dragonBonesData:Object; + + /** + * The xml or JSON for atlas data. + */ + public var textureAtlasData:Object; + + /** + * The non parsed textureAtlas bytes. + */ + public var textureAtlasBytes:ByteArray; + + /** + * TextureAtlas can be bitmap, movieclip, ATF etc. + */ + public var textureAtlas:Object; + + public function DecompressedData() + { + } + + public function dispose():void + { + dragonBonesData = null; + textureAtlasData = null; + textureAtlas = null; + textureAtlasBytes = null; + } + + public function parseTextureAtlasBytes():void + { + var loader:TextureAtlasByteArrayLoader = new TextureAtlasByteArrayLoader(); + loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loaderCompleteHandler); + loader.loadBytes(textureAtlasBytes); + } + + private function loaderCompleteHandler(e:Event):void + { + e.target.removeEventListener(Event.COMPLETE, loaderCompleteHandler); + var loader:Loader = e.target.loader; + var content:Object = e.target.content; + loader.unloadAndStop(); + + if (content is Bitmap) + { + textureAtlas = (content as Bitmap).bitmapData; + } + else if (content is Sprite) + { + textureAtlas = (content as Sprite).getChildAt(0) as MovieClip; + } + else + { + //ATF + textureAtlas = content; + } + + this.dispatchEvent(new Event(Event.COMPLETE)); + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/DisplayData.as b/srclib/dragonBones/objects/DisplayData.as new file mode 100644 index 00000000..b9a1554f --- /dev/null +++ b/srclib/dragonBones/objects/DisplayData.as @@ -0,0 +1,29 @@ +package dragonBones.objects { + + import flash.geom.Point; + + /** @private */ + final public class DisplayData + { + public static const ARMATURE:String = "armature"; + public static const IMAGE:String = "image"; + + public var name:String; + public var slotName:String; + public var type:String; + public var transform:DBTransform; + public var pivot:Point; + + public function DisplayData() + { + transform = new DBTransform(); + pivot = new Point(); + } + + public function dispose():void + { + transform = null; + pivot = null; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/DragonBonesData.as b/srclib/dragonBones/objects/DragonBonesData.as new file mode 100644 index 00000000..afa20bdb --- /dev/null +++ b/srclib/dragonBones/objects/DragonBonesData.as @@ -0,0 +1,118 @@ +package dragonBones.objects +{ + + import flash.utils.Dictionary; + + public class DragonBonesData + { + public var name:String; + public var isGlobalData:Boolean; + + private var _armatureDataList:Vector. = new Vector.(0, true); + private var _displayDataDictionary:Dictionary = new Dictionary(); + + public function DragonBonesData() + { + } + + public function dispose():void + { + for each(var armatureData:ArmatureData in _armatureDataList) + { + armatureData.dispose(); + } + _armatureDataList.fixed = false; + _armatureDataList.length = 0; + _armatureDataList = null; + + removeAllDisplayData(); + _displayDataDictionary = null; + } + + public function get armatureDataList():Vector. + { + return _armatureDataList; + } + + public function getArmatureDataByName(armatureName:String):ArmatureData + { + var i:int = _armatureDataList.length; + while(i --) + { + if(_armatureDataList[i].name == armatureName) + { + return _armatureDataList[i]; + } + } + + return null; + } + + public function addArmatureData(armatureData:ArmatureData):void + { + if(!armatureData) + { + throw new ArgumentError(); + } + + if(_armatureDataList.indexOf(armatureData) < 0) + { + _armatureDataList.fixed = false; + _armatureDataList[_armatureDataList.length] = armatureData; + _armatureDataList.fixed = true; + } + else + { + throw new ArgumentError(); + } + } + + public function removeArmatureData(armatureData:ArmatureData):void + { + var index:int = _armatureDataList.indexOf(armatureData); + if(index >= 0) + { + _armatureDataList.fixed = false; + _armatureDataList.splice(index, 1); + _armatureDataList.fixed = true; + } + } + + public function removeArmatureDataByName(armatureName:String):void + { + var i:int = _armatureDataList.length; + while(i --) + { + if(_armatureDataList[i].name == armatureName) + { + _armatureDataList.fixed = false; + _armatureDataList.splice(i, 1); + _armatureDataList.fixed = true; + } + } + } + + public function getDisplayDataByName(name:String):DisplayData + { + return _displayDataDictionary[name]; + } + + public function addDisplayData(displayData:DisplayData):void + { + _displayDataDictionary[displayData.name] = displayData; + } + + public function removeDisplayDataByName(name:String):void + { + delete _displayDataDictionary[name] + } + + public function removeAllDisplayData():void + { + for(var name:String in _displayDataDictionary) + { + delete _displayDataDictionary[name]; + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/EllipseData.as b/srclib/dragonBones/objects/EllipseData.as new file mode 100644 index 00000000..24e6717d --- /dev/null +++ b/srclib/dragonBones/objects/EllipseData.as @@ -0,0 +1,28 @@ +package dragonBones.objects { + + import flash.geom.Point; + + public final class EllipseData implements IAreaData + { + public var name:String; + + public var width:Number; + public var height:Number; + public var transform:DBTransform; + public var pivot:Point; + + public function EllipseData() + { + width = 0; + height = 0; + transform = new DBTransform(); + pivot = new Point(); + } + + public function dispose():void + { + transform = null; + pivot = null; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/Frame.as b/srclib/dragonBones/objects/Frame.as new file mode 100644 index 00000000..3af82bb6 --- /dev/null +++ b/srclib/dragonBones/objects/Frame.as @@ -0,0 +1,24 @@ +package dragonBones.objects +{ + /** @private */ + public class Frame + { + public var position:int; + public var duration:int; + + public var action:String; + public var event:String; + public var sound:String; + public var curve:CurveData; + + public function Frame() + { + position = 0; + duration = 0; + } + + public function dispose():void + { + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/IAreaData.as b/srclib/dragonBones/objects/IAreaData.as new file mode 100644 index 00000000..dd6de79c --- /dev/null +++ b/srclib/dragonBones/objects/IAreaData.as @@ -0,0 +1,7 @@ +package dragonBones.objects +{ + public interface IAreaData + { + function dispose():void; + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/Object3DataParser.as b/srclib/dragonBones/objects/Object3DataParser.as new file mode 100644 index 00000000..a5f50e0e --- /dev/null +++ b/srclib/dragonBones/objects/Object3DataParser.as @@ -0,0 +1,565 @@ +package dragonBones.objects +{ + + import dragonBones.core.dragonBones_internal; + import dragonBones.utils.ConstValues; + import dragonBones.utils.DBDataUtil; + + import flash.geom.ColorTransform; + import flash.geom.Point; + import flash.utils.Dictionary; + /** + * ... + * @author sukui + */ + final public class Object3DataParser + { + private static var tempDragonBonesData:DragonBonesData; + + use namespace dragonBones_internal; + + public function Object3DataParser() + { + + } + + public static function parseSkeletonData(rawData:Object, ifSkipAnimationData:Boolean=false, outputAnimationDictionary:Dictionary = null):DragonBonesData + { + if(!rawData) + { + throw new ArgumentError(); + } + + var version:String = rawData[ConstValues.A_VERSION]; + switch (version) + { + case "2.3": + case "3.0": + break; + + default: + throw new Error("Nonsupport version!"); + } + + var frameRate:uint = int(rawData[ConstValues.A_FRAME_RATE]); + + var data:DragonBonesData = new DragonBonesData(); + data.name = rawData[ConstValues.A_NAME]; + tempDragonBonesData = data; + var isGlobalData:Boolean = rawData[ConstValues.A_IS_GLOBAL] == "0" ? false : true; + + for each(var armatureObject:Object in rawData[ConstValues.ARMATURE]) + { + data.addArmatureData(parseArmatureData(armatureObject, data, frameRate, isGlobalData, ifSkipAnimationData, outputAnimationDictionary)); + } + + return data; + } + + private static function parseArmatureData(armatureObject:Object, data:DragonBonesData, frameRate:uint, isGlobalData:Boolean, ifSkipAnimationData:Boolean, outputAnimationDictionary:Dictionary):ArmatureData + { + var armatureData:ArmatureData = new ArmatureData(); + armatureData.name = armatureObject[ConstValues.A_NAME]; + + for each(var boneObject:Object in armatureObject[ConstValues.BONE]) + { + armatureData.addBoneData(parseBoneData(boneObject, isGlobalData)); + } + + for each( var skinObj:Object in armatureObject[ConstValues.SKIN]) + { + for each(var slotObj:Object in skinObj[ConstValues.SLOT]) + { + armatureData.addSlotData(parseSlotData(slotObj)); + } + } + + for each(var skinObject:Object in armatureObject[ConstValues.SKIN]) + { + armatureData.addSkinData(parseSkinData(skinObject, data)); + } + + armatureData.sortBoneDataList(); + + if(isGlobalData) + { + DBDataUtil.transformArmatureData(armatureData); + } + + var animationObject:Object; + if(ifSkipAnimationData) + { + if(outputAnimationDictionary!= null) + { + outputAnimationDictionary[armatureData.name] = new Dictionary(); + } + + var index:int = 0; + for each(animationObject in armatureObject[ConstValues.ANIMATION]) + { + if(index == 0) + { + armatureData.addAnimationData(parseAnimationData(animationObject, armatureData, frameRate, isGlobalData)); + } + else if(outputAnimationDictionary != null) + { + outputAnimationDictionary[armatureData.name][animationObject[ConstValues.A_NAME]] = animationObject; + } + index++; + } + } + else + { + for each(animationObject in armatureObject[ConstValues.ANIMATION]) + { + armatureData.addAnimationData(parseAnimationData(animationObject, armatureData, frameRate, isGlobalData)); + } + } + + //for each(var rectangleObject:Object in armatureObject[ConstValues.RECTANGLE]) + //{ + //armatureData.addAreaData(parseRectangleData(rectangleObject)); + //} + // + //for each(var ellipseObject:Object in armatureObject[ConstValues.ELLIPSE]) + //{ + //armatureData.addAreaData(parseEllipseData(ellipseObject)); + //} + + return armatureData; + } + + private static function parseBoneData(boneObject:Object, isGlobalData:Boolean):BoneData + { + var boneData:BoneData = new BoneData(); + boneData.name = boneObject[ConstValues.A_NAME]; + boneData.parent = boneObject[ConstValues.A_PARENT]; + boneData.length = Number(boneObject[ConstValues.A_LENGTH]); + boneData.inheritRotation = getBoolean(boneObject, ConstValues.A_INHERIT_ROTATION, true); + boneData.inheritScale = getBoolean(boneObject, ConstValues.A_INHERIT_SCALE, true); + + parseTransform(boneObject[ConstValues.TRANSFORM], boneData.transform); + if(isGlobalData)//绝对数据 + { + boneData.global.copy(boneData.transform); + } + //for each(var rectangleObject:Object in boneObject[ConstValues.RECTANGLE]) + //{ + //boneObject.addAreaData(parseRectangleData(rectangleObject)); + //} + // + //for each(var ellipseObject:Object in boneObject[ConstValues.ELLIPSE]) + //{ + //boneObject.addAreaData(parseEllipseData(ellipseObject)); + //} + + return boneData; + } + + private static function parseRectangleData(rectangleObject:Object):RectangleData + { + var rectangleData:RectangleData = new RectangleData(); + rectangleData.name = rectangleObject[ConstValues.A_NAME]; + rectangleData.width = Number(rectangleObject[ConstValues.A_WIDTH]); + rectangleData.height = Number(rectangleObject[ConstValues.A_HEIGHT]); + + parseTransform(rectangleObject[ConstValues.TRANSFORM], rectangleData.transform, rectangleData.pivot); + + return rectangleData; + } + + private static function parseEllipseData(ellipseObject:Object):EllipseData + { + var ellipseData:EllipseData = new EllipseData(); + ellipseData.name = ellipseObject[ConstValues.A_NAME]; + ellipseData.width = Number(ellipseObject[ConstValues.A_WIDTH]); + ellipseData.height = Number(ellipseObject[ConstValues.A_HEIGHT]); + + parseTransform(ellipseObject[ConstValues.TRANSFORM], ellipseData.transform, ellipseData.pivot); + + return ellipseData; + } + + private static function parseSlotData(slotObject:Object):SlotData + { + var slotData:SlotData = new SlotData(); + slotData.name = slotObject[ConstValues.A_NAME]; + slotData.parent = slotObject[ConstValues.A_PARENT]; + slotData.zOrder = getNumber(slotObject,ConstValues.A_Z_ORDER,0)||0; + slotData.blendMode = slotObject[ConstValues.A_BLENDMODE]; + slotData.displayIndex = 0; + + return slotData; + } + + private static function parseSkinData(skinObject:Object, data:DragonBonesData):SkinData + { + var skinData:SkinData = new SkinData(); + skinData.name = skinObject[ConstValues.A_NAME]; + + for each(var slotObject:Object in skinObject[ConstValues.SLOT]) + { + skinData.addSlotData(parseSkinSlotData(slotObject, data)); + } + + return skinData; + } + + private static function parseSkinSlotData(slotObject:Object, data:DragonBonesData):SlotData + { + var slotData:SlotData = new SlotData(); + slotData.name = slotObject[ConstValues.A_NAME]; + slotData.parent = slotObject[ConstValues.A_PARENT]; + slotData.zOrder = getNumber(slotObject, ConstValues.A_Z_ORDER, 0) || 0; + slotData.blendMode = slotObject[ConstValues.A_BLENDMODE]; + + for each(var displayObject:Object in slotObject[ConstValues.DISPLAY]) + { + slotData.addDisplayData(parseDisplayData(displayObject, data)); + } + + return slotData; + } + + private static function parseDisplayData(displayObject:Object, data:DragonBonesData):DisplayData + { + var displayData:DisplayData = new DisplayData(); + displayData.name = displayObject[ConstValues.A_NAME]; + displayData.type = displayObject[ConstValues.A_TYPE]; + + //displayData.pivot = data.addSubTexturePivot( + //0, + //0, + //displayData.name + //); + + parseTransform(displayObject[ConstValues.TRANSFORM], displayData.transform, displayData.pivot); + + if (tempDragonBonesData) + { + tempDragonBonesData.addDisplayData(displayData); + } + return displayData; + } + + /** @private */ + dragonBones_internal static function parseAnimationData(animationObject:Object, armatureData:ArmatureData, frameRate:uint, isGlobalData:Boolean):AnimationData + { + var animationData:AnimationData = new AnimationData(); + animationData.name = animationObject[ConstValues.A_NAME]; + animationData.frameRate = frameRate; + animationData.duration = Math.round((Number(animationObject[ConstValues.A_DURATION]) || 1) * 1000 / frameRate); + animationData.playTimes = int(getNumber(animationObject, ConstValues.A_LOOP, 1)); + animationData.fadeTime = getNumber(animationObject, ConstValues.A_FADE_IN_TIME, 0) || 0; + animationData.scale = getNumber(animationObject, ConstValues.A_SCALE, 1) || 0; + //use frame tweenEase, NaN + //overwrite frame tweenEase, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + animationData.tweenEasing = getNumber(animationObject, ConstValues.A_TWEEN_EASING, NaN); + animationData.autoTween = getBoolean(animationObject, ConstValues.A_AUTO_TWEEN, true); + + for each(var frameObject:Object in animationObject[ConstValues.FRAME]) + { + var frame:Frame = parseTransformFrame(frameObject, frameRate, isGlobalData); + animationData.addFrame(frame); + } + + parseTimeline(animationObject, animationData); + + var displayIndexChangeSlotTimelines:Vector. = new Vector.(); + var displayIndexChangeTimelines:Vector. = new Vector.(); + var lastFrameDuration:int = animationData.duration; + for each(var timelineObject:Object in animationObject[ConstValues.TIMELINE]) + { + var timeline:TransformTimeline = parseTransformTimeline(timelineObject, animationData.duration, frameRate, isGlobalData); + lastFrameDuration = Math.min(lastFrameDuration, timeline.frameList[timeline.frameList.length - 1].duration); + animationData.addTimeline(timeline); + + var slotTimeline:SlotTimeline = parseSlotTimeline(timelineObject, animationData.duration, frameRate, isGlobalData); + if (slotTimeline.frameList.length > 0) + { + lastFrameDuration = Math.min(lastFrameDuration, slotTimeline.frameList[slotTimeline.frameList.length - 1].duration); + animationData.addSlotTimeline(slotTimeline); + if (animationData.autoTween) + { + var displayIndexChange:Boolean; + var slotFrame:SlotFrame; + for (var i:int = 0, len:int = slotTimeline.frameList.length; i < len; i++) + { + slotFrame = slotTimeline.frameList[i] as SlotFrame; + if (slotFrame && slotFrame.displayIndex < 0) + { + displayIndexChange = true; + break; + } + } + if (displayIndexChange) + { + displayIndexChangeSlotTimelines.push(slotTimeline); + displayIndexChangeTimelines.push(timeline); + } + } + + } + } + len = displayIndexChangeSlotTimelines.length; + var animationTween:Number = animationData.tweenEasing; + if (len > 0) + { + for (i = 0; i < len; i++) + { + slotTimeline = displayIndexChangeSlotTimelines[i]; + timeline = displayIndexChangeTimelines[i]; + var curFrame:TransformFrame; + var curSlotFrame:SlotFrame; + var nextSlotFrame:SlotFrame; + for (var j:int = 0, jlen:int = slotTimeline.frameList.length; j < jlen; j++) + { + curSlotFrame = slotTimeline.frameList[j] as SlotFrame; + curFrame = timeline.frameList[j] as TransformFrame; + nextSlotFrame = (j == jlen - 1) ? slotTimeline.frameList[0] as SlotFrame : slotTimeline.frameList[j + 1] as SlotFrame; + if (curSlotFrame.displayIndex < 0 || nextSlotFrame.displayIndex < 0) + { + curFrame.tweenEasing = curSlotFrame.tweenEasing = NaN; + } + else if (animationTween == 10) + { + curFrame.tweenEasing = curSlotFrame.tweenEasing = 0; + } + else if (!isNaN(animationTween)) + { + curFrame.tweenEasing = curSlotFrame.tweenEasing = animationTween; + } + else if (curFrame.tweenEasing == 10) + { + curFrame.tweenEasing = 0; + } + } + } + animationData.autoTween = false; + } + if(animationData.frameList.length > 0) + { + lastFrameDuration = Math.min(lastFrameDuration, animationData.frameList[animationData.frameList.length - 1].duration); + } + animationData.lastFrameDuration = lastFrameDuration; + + DBDataUtil.addHideTimeline(animationData, armatureData); + DBDataUtil.transformAnimationData(animationData, armatureData, isGlobalData); + + return animationData; + } + + private static function parseSlotTimeline(timelineObject:Object, duration:int, frameRate:uint, isGlobalData:Boolean):SlotTimeline + { + var timeline:SlotTimeline = new SlotTimeline(); + timeline.name = timelineObject[ConstValues.A_NAME]; + timeline.scale = getNumber(timelineObject, ConstValues.A_SCALE, 1) || 0; + timeline.offset = getNumber(timelineObject, ConstValues.A_OFFSET, 0) || 0; + timeline.duration = duration; + + for each(var frameObject:Object in timelineObject[ConstValues.FRAME]) + { + var frame:SlotFrame = parseSlotFrame(frameObject, frameRate, isGlobalData); + timeline.addFrame(frame); + } + + parseTimeline(timelineObject, timeline); + + return timeline; + } + + private static function parseSlotFrame(frameObject:Object, frameRate:uint, isGlobalData:Boolean):SlotFrame + { + var frame:SlotFrame = new SlotFrame(); + parseFrame(frameObject, frame, frameRate); + + frame.visible = !getBoolean(frameObject, ConstValues.A_HIDE, false); + + //NaN:no tween, 10:auto tween, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + frame.tweenEasing = getNumber(frameObject, ConstValues.A_TWEEN_EASING, 10); + frame.displayIndex = int(getNumber(frameObject,ConstValues.A_DISPLAY_INDEX,0)); + + //如果为NaN,则说明没有改变过zOrder + frame.zOrder = getNumber(frameObject, ConstValues.A_Z_ORDER, isGlobalData ? NaN:0); + + var colorTransformObject:Object = frameObject[ConstValues.COLOR_TRANSFORM]; + if(colorTransformObject) + { + frame.color = new ColorTransform(); + parseColorTransform(colorTransformObject, frame.color); + } + + return frame; + } + + private static function parseTransformTimeline(timelineObject:Object, duration:int, frameRate:uint, isGlobalData:Boolean):TransformTimeline + { + var timeline:TransformTimeline = new TransformTimeline(); + timeline.name = timelineObject[ConstValues.A_NAME]; + timeline.scale = getNumber(timelineObject, ConstValues.A_SCALE, 1) || 0; + timeline.offset = getNumber(timelineObject, ConstValues.A_OFFSET, 0) || 0; + timeline.originPivot.x = getNumber(timelineObject, ConstValues.A_PIVOT_X, 0) || 0; + timeline.originPivot.y = getNumber(timelineObject, ConstValues.A_PIVOT_Y, 0) || 0; + timeline.duration = duration; + + for each(var frameObject:Object in timelineObject[ConstValues.FRAME]) + { + var frame:TransformFrame = parseTransformFrame(frameObject, frameRate, isGlobalData); + timeline.addFrame(frame); + } + + parseTimeline(timelineObject, timeline); + + return timeline; + } + + private static function parseMainFrame(frameObject:Object, frameRate:uint):Frame + { + var frame:Frame = new Frame(); + parseFrame(frameObject, frame, frameRate); + return frame; + } + + private static function parseTransformFrame(frameObject:Object, frameRate:uint, isGlobalData:Boolean):TransformFrame + { + var frame:TransformFrame = new TransformFrame(); + parseFrame(frameObject, frame, frameRate); + + frame.visible = !getBoolean(frameObject, ConstValues.A_HIDE, false); + + //NaN:no tween, 10:auto tween, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + frame.tweenEasing = getNumber(frameObject, ConstValues.A_TWEEN_EASING, 10); + frame.tweenRotate = int(getNumber(frameObject, ConstValues.A_TWEEN_ROTATE, 0)); + frame.tweenScale = getBoolean(frameObject, ConstValues.A_TWEEN_SCALE, true); + //frame.displayIndex = int(getNumber(frameObject, ConstValues.A_DISPLAY_INDEX, 0)); + + //如果为NaN,则说明没有改变过zOrder + //frame.zOrder = getNumber(frameObject, ConstValues.A_Z_ORDER, isGlobalData ? NaN : 0); + + parseTransform(frameObject[ConstValues.TRANSFORM], frame.transform, frame.pivot); + if(isGlobalData)//绝对数据 + { + frame.global.copy(frame.transform); + } + + frame.scaleOffset.x = getNumber(frameObject, ConstValues.A_SCALE_X_OFFSET, 0) || 0; + frame.scaleOffset.y = getNumber(frameObject, ConstValues.A_SCALE_Y_OFFSET, 0) || 0; + + //var colorTransformObject:Object = frameObject[ConstValues.COLOR_TRANSFORM]; + //if(colorTransformObject) + //{ + //frame.color = new ColorTransform(); + //parseColorTransform(colorTransformObject, frame.color); + //} + + return frame; + } + + private static function parseTimeline(timelineObject:Object, timeline:Timeline):void + { + var position:int = 0; + var frame:Frame; + for each(frame in timeline.frameList) + { + frame.position = position; + position += frame.duration; + } + if(frame) + { + frame.duration = timeline.duration - frame.position; + } + } + + private static function parseFrame(frameObject:Object, frame:Frame, frameRate:uint):void + { + frame.duration = Math.round((Number(frameObject[ConstValues.A_DURATION]) || 1) * 1000 / frameRate); + frame.action = frameObject[ConstValues.A_ACTION]; + frame.event = frameObject[ConstValues.A_EVENT]; + frame.sound = frameObject[ConstValues.A_SOUND]; + } + + private static function parseTransform(transformObject:Object, transform:DBTransform, pivot:Point = null):void + { + if(transformObject) + { + if(transform) + { + transform.x = getNumber(transformObject, ConstValues.A_X, 0) || 0; + transform.y = getNumber(transformObject, ConstValues.A_Y, 0) || 0; + transform.skewX = getNumber(transformObject, ConstValues.A_SKEW_X, 0) * ConstValues.ANGLE_TO_RADIAN || 0; + transform.skewY = getNumber(transformObject, ConstValues.A_SKEW_Y, 0) * ConstValues.ANGLE_TO_RADIAN || 0; + transform.scaleX = getNumber(transformObject, ConstValues.A_SCALE_X, 1) || 0; + transform.scaleY = getNumber(transformObject, ConstValues.A_SCALE_Y, 1) || 0; + } + if(pivot) + { + pivot.x = getNumber(transformObject, ConstValues.A_PIVOT_X, 0) || 0; + pivot.y = getNumber(transformObject, ConstValues.A_PIVOT_Y, 0) || 0; + } + } + } + + private static function parseColorTransform(colorTransformObject:Object, colorTransform:ColorTransform):void + { + if(colorTransformObject) + { + if(colorTransform) + { + colorTransform.alphaOffset = int(colorTransformObject[ConstValues.A_ALPHA_OFFSET]); + colorTransform.redOffset = int(colorTransformObject[ConstValues.A_RED_OFFSET]); + colorTransform.greenOffset = int(colorTransformObject[ConstValues.A_GREEN_OFFSET]); + colorTransform.blueOffset = int(colorTransformObject[ConstValues.A_BLUE_OFFSET]); + + colorTransform.alphaMultiplier = int(getNumber(colorTransformObject, ConstValues.A_ALPHA_MULTIPLIER,100)) * 0.01; + colorTransform.redMultiplier = int(getNumber(colorTransformObject,ConstValues.A_RED_MULTIPLIER,100)) * 0.01; + colorTransform.greenMultiplier = int(getNumber(colorTransformObject,ConstValues.A_GREEN_MULTIPLIER,100)) * 0.01; + colorTransform.blueMultiplier = int(getNumber(colorTransformObject,ConstValues.A_BLUE_MULTIPLIER,100)) * 0.01; + } + } + } + + private static function getBoolean(data:Object, key:String, defaultValue:Boolean):Boolean + { + if(data && key in data) + { + switch(String(data[key])) + { + case "0": + case "NaN": + case "": + case "false": + case "null": + case "undefined": + return false; + + case "1": + case "true": + default: + return true; + } + } + return defaultValue; + } + + private static function getNumber(data:Object, key:String, defaultValue:Number):Number + { + if(data && key in data) + { + switch(String(data[key])) + { + case "NaN": + case "": + case "false": + case "null": + case "undefined": + return NaN; + + default: + return Number(data[key]); + } + } + return defaultValue; + } + } + +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/ObjectDataParser.as b/srclib/dragonBones/objects/ObjectDataParser.as new file mode 100644 index 00000000..091c2222 --- /dev/null +++ b/srclib/dragonBones/objects/ObjectDataParser.as @@ -0,0 +1,476 @@ +package dragonBones.objects { + + import dragonBones.core.DragonBones; + import dragonBones.core.dragonBones_internal; + import dragonBones.textures.TextureData; + import dragonBones.utils.ConstValues; + import dragonBones.utils.DBDataUtil; + + import flash.geom.ColorTransform; + import flash.geom.Point; + import flash.geom.Rectangle; + + use namespace dragonBones_internal; + + public final class ObjectDataParser + { + private static var tempDragonBonesData:DragonBonesData; + + public static function parseTextureAtlasData(rawData:Object, scale:Number = 1):Object + { + var textureAtlasData:Object = {}; + textureAtlasData.__name = rawData[ConstValues.A_NAME]; + var subTextureFrame:Rectangle; + for each (var subTextureObject:Object in rawData[ConstValues.SUB_TEXTURE]) + { + var subTextureName:String = subTextureObject[ConstValues.A_NAME]; + var subTextureRegion:Rectangle = new Rectangle(); + subTextureRegion.x = int(subTextureObject[ConstValues.A_X]) / scale; + subTextureRegion.y = int(subTextureObject[ConstValues.A_Y]) / scale; + subTextureRegion.width = int(subTextureObject[ConstValues.A_WIDTH]) / scale; + subTextureRegion.height = int(subTextureObject[ConstValues.A_HEIGHT]) / scale; + + var rotated:Boolean = subTextureObject[ConstValues.A_ROTATED] == "true"; + + var frameWidth:Number = int(subTextureObject[ConstValues.A_FRAME_WIDTH]) / scale; + var frameHeight:Number = int(subTextureObject[ConstValues.A_FRAME_HEIGHT]) / scale; + + if(frameWidth > 0 && frameHeight > 0) + { + subTextureFrame = new Rectangle(); + subTextureFrame.x = int(subTextureObject[ConstValues.A_FRAME_X]) / scale; + subTextureFrame.y = int(subTextureObject[ConstValues.A_FRAME_Y]) / scale; + subTextureFrame.width = frameWidth; + subTextureFrame.height = frameHeight; + } + else + { + subTextureFrame = null; + } + + textureAtlasData[subTextureName] = new TextureData(subTextureRegion, subTextureFrame, rotated); + } + + return textureAtlasData; + } + + public static function parseDragonBonesData(rawDataToParse:Object):DragonBonesData + { + if(!rawDataToParse) + { + throw new ArgumentError(); + } + + var version:String = rawDataToParse[ConstValues.A_VERSION]; + switch (version) + { + case "2.3": + case "3.0": + return Object3DataParser.parseSkeletonData(rawDataToParse); + break; + case DragonBones.DATA_VERSION: + break; + + default: + throw new Error("Nonsupport version!"); + } + + var frameRate:uint = int(rawDataToParse[ConstValues.A_FRAME_RATE]); + + var outputDragonBonesData:DragonBonesData = new DragonBonesData(); + outputDragonBonesData.name = rawDataToParse[ConstValues.A_NAME]; + outputDragonBonesData.isGlobalData = rawDataToParse[ConstValues.A_IS_GLOBAL] == "0" ? false : true; + tempDragonBonesData = outputDragonBonesData; + + for each(var armatureObject:Object in rawDataToParse[ConstValues.ARMATURE]) + { + outputDragonBonesData.addArmatureData(parseArmatureData(armatureObject, frameRate)); + } + + tempDragonBonesData = null; + + return outputDragonBonesData; + } + + private static function parseArmatureData(armatureDataToParse:Object, frameRate:uint):ArmatureData + { + var outputArmatureData:ArmatureData = new ArmatureData(); + outputArmatureData.name = armatureDataToParse[ConstValues.A_NAME]; + + for each(var boneObject:Object in armatureDataToParse[ConstValues.BONE]) + { + outputArmatureData.addBoneData(parseBoneData(boneObject)); + } + + for each(var slotObject:Object in armatureDataToParse[ConstValues.SLOT]) + { + outputArmatureData.addSlotData(parseSlotData(slotObject)); + } + + for each(var skinObject:Object in armatureDataToParse[ConstValues.SKIN]) + { + outputArmatureData.addSkinData(parseSkinData(skinObject)); + } + + if(tempDragonBonesData.isGlobalData) + { + DBDataUtil.transformArmatureData(outputArmatureData); + } + + outputArmatureData.sortBoneDataList(); + + for each(var animationObject:Object in armatureDataToParse[ConstValues.ANIMATION]) + { + var animationData:AnimationData = parseAnimationData(animationObject, frameRate); + DBDataUtil.addHideTimeline(animationData, outputArmatureData); + DBDataUtil.transformAnimationData(animationData, outputArmatureData, tempDragonBonesData.isGlobalData); + outputArmatureData.addAnimationData(animationData); + } + + return outputArmatureData; + } + + //把bone的初始transform解析并返回 + private static function parseBoneData(boneObject:Object):BoneData + { + var boneData:BoneData = new BoneData(); + boneData.name = boneObject[ConstValues.A_NAME]; + boneData.parent = boneObject[ConstValues.A_PARENT]; + boneData.length = Number(boneObject[ConstValues.A_LENGTH]); + boneData.inheritRotation = getBoolean(boneObject, ConstValues.A_INHERIT_ROTATION, true); + boneData.inheritScale = getBoolean(boneObject, ConstValues.A_INHERIT_SCALE, true); + + parseTransform(boneObject[ConstValues.TRANSFORM], boneData.transform); + if(tempDragonBonesData.isGlobalData)//绝对数据 + { + boneData.global.copy(boneData.transform); + } + return boneData; + } + + private static function parseSkinData(skinObject:Object):SkinData + { + var skinData:SkinData = new SkinData(); + skinData.name = skinObject[ConstValues.A_NAME]; + + for each(var slotObject:Object in skinObject[ConstValues.SLOT]) + { + skinData.addSlotData(parseSlotDisplayData(slotObject)); + } + + return skinData; + } + + private static function parseSlotDisplayData(slotObject:Object):SlotData + { + var slotData:SlotData = new SlotData(); + slotData.name = slotObject[ConstValues.A_NAME]; + for each(var displayObject:Object in slotObject[ConstValues.DISPLAY]) + { + slotData.addDisplayData(parseDisplayData(displayObject)); + } + + return slotData; + } + + private static function parseSlotData(slotObject:Object):SlotData + { + var slotData:SlotData = new SlotData(); + slotData.name = slotObject[ConstValues.A_NAME]; + slotData.parent = slotObject[ConstValues.A_PARENT]; + slotData.zOrder = getNumber(slotObject, ConstValues.A_Z_ORDER, 0) || 0; + slotData.blendMode = slotObject[ConstValues.A_BLENDMODE]; + slotData.displayIndex = slotObject[ConstValues.A_DISPLAY_INDEX]; + //for each(var displayObject:Object in slotObject[ConstValues.DISPLAY]) + //{ + //slotData.addDisplayData(parseDisplayData(displayObject)); + //} + + return slotData; + } + + private static function parseDisplayData(displayObject:Object):DisplayData + { + var displayData:DisplayData = new DisplayData(); + displayData.name = displayObject[ConstValues.A_NAME]; + displayData.type = displayObject[ConstValues.A_TYPE]; + parseTransform(displayObject[ConstValues.TRANSFORM], displayData.transform, displayData.pivot); + displayData.pivot.x = NaN; + displayData.pivot.y = NaN; + if(tempDragonBonesData!=null) + { + tempDragonBonesData.addDisplayData(displayData); + } + + return displayData; + } + + /** @private */ + dragonBones_internal static function parseAnimationData(animationObject:Object, frameRate:uint):AnimationData + { + var animationData:AnimationData = new AnimationData(); + animationData.name = animationObject[ConstValues.A_NAME]; + animationData.frameRate = frameRate; + animationData.duration = Math.round((Number(animationObject[ConstValues.A_DURATION]) || 1) * 1000 / frameRate); + animationData.playTimes = int(getNumber(animationObject, ConstValues.A_PLAY_TIMES, 1)); + animationData.fadeTime = getNumber(animationObject, ConstValues.A_FADE_IN_TIME, 0) || 0; + animationData.scale = getNumber(animationObject, ConstValues.A_SCALE, 1) || 0; + //use frame tweenEase, NaN + //overwrite frame tweenEase, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + animationData.tweenEasing = getNumber(animationObject, ConstValues.A_TWEEN_EASING, NaN); + animationData.autoTween = getBoolean(animationObject, ConstValues.A_AUTO_TWEEN, true); + + for each(var frameObject:Object in animationObject[ConstValues.FRAME]) + { + var frame:Frame = parseTransformFrame(frameObject, frameRate); + animationData.addFrame(frame); + } + + parseTimeline(animationObject, animationData); + + var lastFrameDuration:int = animationData.duration; + for each(var timelineObject:Object in animationObject[ConstValues.BONE]) + { + var timeline:TransformTimeline = parseTransformTimeline(timelineObject, animationData.duration, frameRate); + if (timeline.frameList.length > 0) + { + lastFrameDuration = Math.min(lastFrameDuration, timeline.frameList[timeline.frameList.length - 1].duration); + animationData.addTimeline(timeline); + } + + } + + for each(var slotTimelineObject:Object in animationObject[ConstValues.SLOT]) + { + var slotTimeline:SlotTimeline = parseSlotTimeline(slotTimelineObject, animationData.duration, frameRate); + if (slotTimeline.frameList.length > 0) + { + lastFrameDuration = Math.min(lastFrameDuration, slotTimeline.frameList[slotTimeline.frameList.length - 1].duration); + animationData.addSlotTimeline(slotTimeline); + } + + } + + if(animationData.frameList.length > 0) + { + lastFrameDuration = Math.min(lastFrameDuration, animationData.frameList[animationData.frameList.length - 1].duration); + } + //取得timeline中最小的lastFrameDuration并保存 + animationData.lastFrameDuration = lastFrameDuration; + + return animationData; + } + + private static function parseTransformTimeline(timelineObject:Object, duration:int, frameRate:uint):TransformTimeline + { + var outputTimeline:TransformTimeline = new TransformTimeline(); + outputTimeline.name = timelineObject[ConstValues.A_NAME]; + outputTimeline.scale = getNumber(timelineObject, ConstValues.A_SCALE, 1) || 0; + outputTimeline.offset = getNumber(timelineObject, ConstValues.A_OFFSET, 0) || 0; + outputTimeline.originPivot.x = getNumber(timelineObject, ConstValues.A_PIVOT_X, 0) || 0; + outputTimeline.originPivot.y = getNumber(timelineObject, ConstValues.A_PIVOT_Y, 0) || 0; + outputTimeline.duration = duration; + + for each(var frameObject:Object in timelineObject[ConstValues.FRAME]) + { + var frame:TransformFrame = parseTransformFrame(frameObject, frameRate); + outputTimeline.addFrame(frame); + } + + parseTimeline(timelineObject, outputTimeline); + + return outputTimeline; + } + + private static function parseSlotTimeline(timelineObject:Object, duration:int, frameRate:uint):SlotTimeline + { + var timeline:SlotTimeline = new SlotTimeline(); + timeline.name = timelineObject[ConstValues.A_NAME]; + timeline.scale = getNumber(timelineObject, ConstValues.A_SCALE, 1) || 0; + timeline.offset = getNumber(timelineObject, ConstValues.A_OFFSET, 0) || 0; + //timeline.originPivot.x = getNumber(timelineXML, ConstValues.A_PIVOT_X, 0) || 0; + //timeline.originPivot.y = getNumber(timelineXML, ConstValues.A_PIVOT_Y, 0) || 0; + timeline.duration = duration; + + for each(var frameObject:Object in timelineObject[ConstValues.FRAME]) + { + var frame:SlotFrame = parseSlotFrame(frameObject, frameRate); + timeline.addFrame(frame); + } + + parseTimeline(timelineObject, timeline); + + return timeline; + } + + private static function parseMainFrame(frameObject:Object, frameRate:uint):Frame + { + var frame:Frame = new Frame(); + parseFrame(frameObject, frame, frameRate); + return frame; + } + + private static function parseTransformFrame(frameObject:Object, frameRate:uint):TransformFrame + { + var outputFrame:TransformFrame = new TransformFrame(); + parseFrame(frameObject, outputFrame, frameRate); + + outputFrame.visible = !getBoolean(frameObject, ConstValues.A_HIDE, false); + + //NaN:no tween, 10:auto tween, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + outputFrame.tweenEasing = getNumber(frameObject, ConstValues.A_TWEEN_EASING, 10); + outputFrame.tweenRotate = int(getNumber(frameObject, ConstValues.A_TWEEN_ROTATE, 0)); + outputFrame.tweenScale = getBoolean(frameObject, ConstValues.A_TWEEN_SCALE, true); +// outputFrame.displayIndex = int(getNumber(frameObject, ConstValues.A_DISPLAY_INDEX, 0)); + + parseTransform(frameObject[ConstValues.TRANSFORM], outputFrame.transform, outputFrame.pivot); + if(tempDragonBonesData.isGlobalData)//绝对数据 + { + outputFrame.global.copy(outputFrame.transform); + } + + outputFrame.scaleOffset.x = getNumber(frameObject, ConstValues.A_SCALE_X_OFFSET, 0) || 0; + outputFrame.scaleOffset.y = getNumber(frameObject, ConstValues.A_SCALE_Y_OFFSET, 0) || 0; + return outputFrame; + } + + private static function parseSlotFrame(frameObject:Object, frameRate:uint):SlotFrame + { + var frame:SlotFrame = new SlotFrame(); + parseFrame(frameObject, frame, frameRate); + + frame.visible = !getBoolean(frameObject, ConstValues.A_HIDE, false); + + //NaN:no tween, 10:auto tween, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + frame.tweenEasing = getNumber(frameObject, ConstValues.A_TWEEN_EASING, 10); + frame.displayIndex = int(getNumber(frameObject,ConstValues.A_DISPLAY_INDEX,0)); + + //如果为NaN,则说明没有改变过zOrder + frame.zOrder = getNumber(frameObject, ConstValues.A_Z_ORDER, tempDragonBonesData.isGlobalData ? NaN:0); + + var colorTransformObject:Object = frameObject[ConstValues.COLOR]; + if(colorTransformObject) + { + frame.color = new ColorTransform(); + parseColorTransform(colorTransformObject, frame.color); + } + + return frame; + } + + private static function parseTimeline(timelineObject:Object, outputTimeline:Timeline):void + { + var position:int = 0; + var frame:Frame; + for each(frame in outputTimeline.frameList) + { + frame.position = position; + position += frame.duration; + } + //防止duration计算有误差 + if(frame) + { + frame.duration = outputTimeline.duration - frame.position; + } + } + + private static function parseFrame(frameObject:Object, outputFrame:Frame, frameRate:uint):void + { + outputFrame.duration = Math.round((Number(frameObject[ConstValues.A_DURATION])) * 1000 / frameRate); + outputFrame.action = frameObject[ConstValues.A_ACTION]; + outputFrame.event = frameObject[ConstValues.A_EVENT]; + outputFrame.sound = frameObject[ConstValues.A_SOUND]; + if (frameObject[ConstValues.A_CURVE] != null && frameObject[ConstValues.A_CURVE].length == 4) + { + outputFrame.curve = new CurveData(); + outputFrame.curve.pointList = [new Point(frameObject[ConstValues.A_CURVE][0], + frameObject[ConstValues.A_CURVE][1]), + new Point(frameObject[ConstValues.A_CURVE][2], + frameObject[ConstValues.A_CURVE][3])]; + } + } + + private static function parseTransform(transformObject:Object, transform:DBTransform, pivot:Point = null):void + { + if(transformObject) + { + if(transform) + { + transform.x = getNumber(transformObject,ConstValues.A_X,0) || 0; + transform.y = getNumber(transformObject,ConstValues.A_Y,0) || 0; + transform.skewX = getNumber(transformObject,ConstValues.A_SKEW_X,0) * ConstValues.ANGLE_TO_RADIAN || 0; + transform.skewY = getNumber(transformObject,ConstValues.A_SKEW_Y,0) * ConstValues.ANGLE_TO_RADIAN || 0; + transform.scaleX = getNumber(transformObject, ConstValues.A_SCALE_X, 1) || 0; + transform.scaleY = getNumber(transformObject, ConstValues.A_SCALE_Y, 1) || 0; + } + if(pivot) + { + pivot.x = getNumber(transformObject,ConstValues.A_PIVOT_X,0) || 0; + pivot.y = getNumber(transformObject,ConstValues.A_PIVOT_Y,0) || 0; + } + } + } + + private static function parseColorTransform(colorTransformObject:Object, colorTransform:ColorTransform):void + { + if(colorTransformObject) + { + if(colorTransform) + { + colorTransform.alphaOffset = int(colorTransformObject[ConstValues.A_ALPHA_OFFSET]); + colorTransform.redOffset = int(colorTransformObject[ConstValues.A_RED_OFFSET]); + colorTransform.greenOffset = int(colorTransformObject[ConstValues.A_GREEN_OFFSET]); + colorTransform.blueOffset = int(colorTransformObject[ConstValues.A_BLUE_OFFSET]); + + colorTransform.alphaMultiplier = int(getNumber(colorTransformObject, ConstValues.A_ALPHA_MULTIPLIER,100)) * 0.01; + colorTransform.redMultiplier = int(getNumber(colorTransformObject,ConstValues.A_RED_MULTIPLIER,100)) * 0.01; + colorTransform.greenMultiplier = int(getNumber(colorTransformObject,ConstValues.A_GREEN_MULTIPLIER,100)) * 0.01; + colorTransform.blueMultiplier = int(getNumber(colorTransformObject,ConstValues.A_BLUE_MULTIPLIER,100)) * 0.01; + } + } + } + + private static function getBoolean(data:Object, key:String, defaultValue:Boolean):Boolean + { + if(data && key in data) + { + switch(String(data[key])) + { + case "0": + case "NaN": + case "": + case "false": + case "null": + case "undefined": + return false; + + case "1": + case "true": + default: + return true; + } + } + return defaultValue; + } + + private static function getNumber(data:Object, key:String, defaultValue:Number):Number + { + if(data && key in data) + { + switch(String(data[key])) + { + case "NaN": + case "": + case "false": + case "null": + case "undefined": + return NaN; + + default: + return Number(data[key]); + } + } + return defaultValue; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/RectangleData.as b/srclib/dragonBones/objects/RectangleData.as new file mode 100644 index 00000000..8cdb1ea5 --- /dev/null +++ b/srclib/dragonBones/objects/RectangleData.as @@ -0,0 +1,28 @@ +package dragonBones.objects { + + import flash.geom.Point; + + public final class RectangleData implements IAreaData + { + public var name:String; + + public var width:Number; + public var height:Number; + public var transform:DBTransform; + public var pivot:Point; + + public function RectangleData() + { + width = 0; + height = 0; + transform = new DBTransform(); + pivot = new Point(); + } + + public function dispose():void + { + transform = null; + pivot = null; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/SkinData.as b/srclib/dragonBones/objects/SkinData.as new file mode 100644 index 00000000..488adef4 --- /dev/null +++ b/srclib/dragonBones/objects/SkinData.as @@ -0,0 +1,64 @@ +package dragonBones.objects +{ + /** @private */ + final public class SkinData + { + public var name:String; + + private var _slotDataList:Vector.; + + public function SkinData() + { + _slotDataList = new Vector.(0, true); + } + + public function dispose():void + { + var i:int = _slotDataList.length; + while(i --) + { + _slotDataList[i].dispose(); + } + _slotDataList.fixed = false; + _slotDataList.length = 0; + _slotDataList = null; + } + + public function getSlotData(slotName:String):SlotData + { + var i:int = _slotDataList.length; + while(i --) + { + if(_slotDataList[i].name == slotName) + { + return _slotDataList[i]; + } + } + return null; + } + + public function addSlotData(slotData:SlotData):void + { + if(!slotData) + { + throw new ArgumentError(); + } + + if (_slotDataList.indexOf(slotData) < 0) + { + _slotDataList.fixed = false; + _slotDataList[_slotDataList.length] = slotData; + _slotDataList.fixed = true; + } + else + { + throw new ArgumentError(); + } + } + + public function get slotDataList():Vector. + { + return _slotDataList; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/SlotData.as b/srclib/dragonBones/objects/SlotData.as new file mode 100644 index 00000000..cce23541 --- /dev/null +++ b/srclib/dragonBones/objects/SlotData.as @@ -0,0 +1,63 @@ +package dragonBones.objects +{ + /** @private */ + public final class SlotData + { + public var name:String; + public var parent:String; + public var zOrder:Number; + public var blendMode:String; + public var displayIndex:int; + + private var _displayDataList:Vector.; + + public function SlotData() + { + _displayDataList = new Vector.(0, true); + zOrder = 0; + } + + public function dispose():void + { + _displayDataList.fixed = false; + _displayDataList.length = 0; + } + + public function addDisplayData(displayData:DisplayData):void + { + if(!displayData) + { + throw new ArgumentError(); + } + if (_displayDataList.indexOf(displayData) < 0) + { + _displayDataList.fixed = false; + _displayDataList[_displayDataList.length] = displayData; + _displayDataList.fixed = true; + } + else + { + throw new ArgumentError(); + } + } + + public function getDisplayData(displayName:String):DisplayData + { + var i:int = _displayDataList.length; + while(i --) + { + if(_displayDataList[i].name == displayName) + { + return _displayDataList[i]; + } + } + + return null; + } + + public function get displayDataList():Vector. + { + return _displayDataList; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/SlotFrame.as b/srclib/dragonBones/objects/SlotFrame.as new file mode 100644 index 00000000..a6f3b58e --- /dev/null +++ b/srclib/dragonBones/objects/SlotFrame.as @@ -0,0 +1,45 @@ +package dragonBones.objects { + + import flash.geom.ColorTransform; + + /** @private */ + final public class SlotFrame extends Frame + { + //NaN:no tween, 10:auto tween, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + public var tweenEasing:Number; + public var displayIndex:int; + public var visible:Boolean; + public var zOrder:Number; + public var color:ColorTransform; + + + public function SlotFrame() + { + super(); + + tweenEasing = 10; + displayIndex = 0; + visible = true; + zOrder = NaN; + } + + override public function dispose():void + { + super.dispose(); + color = null; + } + + public function get colorChanged():Boolean + { + if(color && (color.alphaMultiplier != 1 || color.alphaOffset != 0 || + color.blueMultiplier != 1 || color.blueOffset != 0 || + color.greenMultiplier != 1 || color.greenOffset != 0 || + color.redMultiplier != 1 || color.redOffset != 0)) + { + return true; + } + return false; + } + } + +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/SlotTimeline.as b/srclib/dragonBones/objects/SlotTimeline.as new file mode 100644 index 00000000..76b45ef7 --- /dev/null +++ b/srclib/dragonBones/objects/SlotTimeline.as @@ -0,0 +1,21 @@ +package dragonBones.objects { + + public final class SlotTimeline extends Timeline + { + public var name:String; + public var transformed:Boolean; + + public var offset:Number; + + public function SlotTimeline() + { + super(); + offset = 0; + } + + override public function dispose():void + { + super.dispose(); + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/TextureAtlasByteArrayLoader.as b/srclib/dragonBones/objects/TextureAtlasByteArrayLoader.as new file mode 100644 index 00000000..8ea354ce --- /dev/null +++ b/srclib/dragonBones/objects/TextureAtlasByteArrayLoader.as @@ -0,0 +1,24 @@ +package dragonBones.objects { + + import flash.display.Loader; + import flash.system.ApplicationDomain; + import flash.system.LoaderContext; + import flash.utils.ByteArray; + + public class TextureAtlasByteArrayLoader extends Loader + { + private static const loaderContext:LoaderContext = new LoaderContext(false, ApplicationDomain.currentDomain); + + public function TextureAtlasByteArrayLoader() + { + super(); + loaderContext.allowCodeImport = true; + } + + override public function loadBytes(bytes:ByteArray, context:LoaderContext=null):void + { + context = context == null ? loaderContext : context; + super.loadBytes(bytes, context); + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/Timeline.as b/srclib/dragonBones/objects/Timeline.as new file mode 100644 index 00000000..b5f97060 --- /dev/null +++ b/srclib/dragonBones/objects/Timeline.as @@ -0,0 +1,53 @@ +package dragonBones.objects +{ + public class Timeline + { + public var duration:int; + public var scale:Number; + + private var _frameList:Vector.; + + public function Timeline() + { + _frameList = new Vector.(0, true); + duration = 0; + scale = 1; + } + + public function dispose():void + { + var i:int = _frameList.length; + while(i --) + { + _frameList[i].dispose(); + } + _frameList.fixed = false; + _frameList.length = 0; + _frameList = null; + } + + public function addFrame(frame:Frame):void + { + if(!frame) + { + throw new ArgumentError(); + } + + if(_frameList.indexOf(frame) < 0) + { + _frameList.fixed = false; + _frameList[_frameList.length] = frame; + _frameList.fixed = true; + } + else + { + throw new ArgumentError(); + } + } + + public function get frameList():Vector. + { + return _frameList; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/TransformFrame.as b/srclib/dragonBones/objects/TransformFrame.as new file mode 100644 index 00000000..df897082 --- /dev/null +++ b/srclib/dragonBones/objects/TransformFrame.as @@ -0,0 +1,47 @@ +package dragonBones.objects { + + import flash.geom.Point; + + /** @private */ + final public class TransformFrame extends Frame + { + //NaN:no tween, 10:auto tween, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + public var tweenEasing:Number; + public var tweenRotate:int; + public var tweenScale:Boolean; +// public var displayIndex:int; + public var visible:Boolean; + + public var global:DBTransform; + public var transform:DBTransform; + public var pivot:Point; + public var scaleOffset:Point; + + + public function TransformFrame() + { + super(); + + tweenEasing = 10; + tweenRotate = 0; +// tweenScale = true; +// displayIndex = 0; + visible = true; + + global = new DBTransform(); + transform = new DBTransform(); + pivot = new Point(); + scaleOffset = new Point(); + } + + override public function dispose():void + { + super.dispose(); + global = null; + transform = null; + pivot = null; + scaleOffset = null; + } + } + +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/TransformTimeline.as b/srclib/dragonBones/objects/TransformTimeline.as new file mode 100644 index 00000000..71f250a2 --- /dev/null +++ b/srclib/dragonBones/objects/TransformTimeline.as @@ -0,0 +1,38 @@ +package dragonBones.objects { + + import flash.geom.Point; + + public final class TransformTimeline extends Timeline + { + public var name:String; + public var transformed:Boolean; + + //第一帧的Transform + public var originTransform:DBTransform; + + //第一帧的骨头的轴点 + public var originPivot:Point; + + public var offset:Number; + + public function TransformTimeline() + { + super(); + + originTransform = new DBTransform(); + originTransform.scaleX = 1; + originTransform.scaleY = 1; + + originPivot = new Point(); + offset = 0; + } + + override public function dispose():void + { + super.dispose(); + + originTransform = null; + originPivot = null; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/XML3DataParser.as b/srclib/dragonBones/objects/XML3DataParser.as new file mode 100644 index 00000000..e2766f8c --- /dev/null +++ b/srclib/dragonBones/objects/XML3DataParser.as @@ -0,0 +1,516 @@ +package dragonBones.objects +{ + + import dragonBones.core.dragonBones_internal; + import dragonBones.utils.ConstValues; + import dragonBones.utils.DBDataUtil; + + import flash.geom.ColorTransform; + import flash.geom.Point; + import flash.utils.Dictionary; + /** + * ... + * @author sukui + */ + final public class XML3DataParser + { + private static var tempDragonBonesData:DragonBonesData; + + use namespace dragonBones_internal; + + public function XML3DataParser() + { + + } + + /** + * Parse the SkeletonData. + * @param xml The SkeletonData xml to parse. + * @return A SkeletonData instance. + */ + public static function parseSkeletonData(rawData:XML, ifSkipAnimationData:Boolean = false, outputAnimationDictionary:Dictionary = null):DragonBonesData + { + if(!rawData) + { + throw new ArgumentError(); + } + var version:String = rawData.@[ConstValues.A_VERSION]; + switch (version) + { + case "2.3": + //Update2_3To3_0.format(rawData as XML); + break; + + case "3.0": + break; + + default: + throw new Error("Nonsupport version!"); + } + + var frameRate:uint = int(rawData.@[ConstValues.A_FRAME_RATE]); + + var data:DragonBonesData = new DragonBonesData(); + tempDragonBonesData = data; + data.name = rawData.@[ConstValues.A_NAME]; + var isGlobalData:Boolean = rawData.@[ConstValues.A_IS_GLOBAL] == "0" ? false : true; + for each(var armatureXML:XML in rawData[ConstValues.ARMATURE]) + { + data.addArmatureData(parseArmatureData(armatureXML, data, frameRate, isGlobalData, ifSkipAnimationData, outputAnimationDictionary)); + } + + return data; + } + + private static function parseArmatureData(armatureXML:XML, data:DragonBonesData, frameRate:uint, isGlobalData:Boolean, ifSkipAnimationData:Boolean, outputAnimationDictionary:Dictionary):ArmatureData + { + var armatureData:ArmatureData = new ArmatureData(); + armatureData.name = armatureXML.@[ConstValues.A_NAME]; + + for each(var boneXML:XML in armatureXML[ConstValues.BONE]) + { + armatureData.addBoneData(parseBoneData(boneXML, isGlobalData)); + } + + for each( var skinXml:XML in armatureXML[ConstValues.SKIN]) + { + for each(var slotXML:XML in skinXml[ConstValues.SLOT]) + { + armatureData.addSlotData(parseSlotData(slotXML)); + } + } + for each(var skinXML:XML in armatureXML[ConstValues.SKIN]) + { + armatureData.addSkinData(parseSkinData(skinXML, data)); + } + + if(isGlobalData) + { + DBDataUtil.transformArmatureData(armatureData); + } + armatureData.sortBoneDataList(); + + var animationXML:XML; + if(ifSkipAnimationData) + { + //if(outputAnimationDictionary!= null) + //{ + //outputAnimationDictionary[armatureData.name] = new Dictionary(); + //} + // + //var index:int = 0; + //for each(animationXML in armatureXML[ConstValues.ANIMATION]) + //{ + //if(index == 0) + //{ + //armatureData.addAnimationData(parseAnimationData(animationXML, armatureData, frameRate, isGlobalData)); + //} + //else if(outputAnimationDictionary != null) + //{ + //outputAnimationDictionary[armatureData.name][animationXML.@[ConstValues.A_NAME]] = animationXML; + //} + //index++; + //} + } + else + { + for each(animationXML in armatureXML[ConstValues.ANIMATION]) + { + //var animationData:AnimationData = parseAnimationData(animationXML, frameRate); + //DBDataUtil.addHideTimeline(animationData, outputArmatureData); + //DBDataUtil.transformAnimationData(animationData, outputArmatureData, tempDragonBonesData.isGlobalData); + //outputArmatureData.addAnimationData(animationData); + armatureData.addAnimationData(parseAnimationData(animationXML, armatureData, frameRate, isGlobalData)); + } + } + + //for each(var rectangleXML:XML in armatureXML[ConstValues.RECTANGLE]) + //{ + //armatureData.addAreaData(parseRectangleData(rectangleXML)); + //} + // + //for each(var ellipseXML:XML in armatureXML[ConstValues.ELLIPSE]) + //{ + //armatureData.addAreaData(parseEllipseData(ellipseXML)); + //} + + return armatureData; + } + + private static function parseBoneData(boneXML:XML, isGlobalData:Boolean):BoneData + { + var boneData:BoneData = new BoneData(); + boneData.name = boneXML.@[ConstValues.A_NAME]; + boneData.parent = boneXML.@[ConstValues.A_PARENT]; + boneData.length = Number(boneXML.@[ConstValues.A_LENGTH]); + boneData.inheritRotation = getBoolean(boneXML, ConstValues.A_INHERIT_ROTATION, true); + boneData.inheritScale = getBoolean(boneXML, ConstValues.A_INHERIT_SCALE, true); + + parseTransform(boneXML[ConstValues.TRANSFORM][0], boneData.transform); + if(isGlobalData)//绝对数据 + { + boneData.global.copy(boneData.transform); + } + + //for each(var rectangleXML:XML in boneXML[ConstValues.RECTANGLE]) + //{ + //boneData.addAreaData(parseRectangleData(rectangleXML)); + //} + // + //for each(var ellipseXML:XML in boneXML[ConstValues.ELLIPSE]) + //{ + //boneData.addAreaData(parseEllipseData(ellipseXML)); + //} + + return boneData; + } + + private static function parseRectangleData(rectangleXML:XML):RectangleData + { + var rectangleData:RectangleData = new RectangleData(); + rectangleData.name = rectangleXML.@[ConstValues.A_NAME]; + rectangleData.width = Number(rectangleXML.@[ConstValues.A_WIDTH]); + rectangleData.height = Number(rectangleXML.@[ConstValues.A_HEIGHT]); + + parseTransform(rectangleXML[ConstValues.TRANSFORM][0], rectangleData.transform, rectangleData.pivot); + + return rectangleData; + } + + private static function parseEllipseData(ellipseXML:XML):EllipseData + { + var ellipseData:EllipseData = new EllipseData(); + ellipseData.name = ellipseXML.@[ConstValues.A_NAME]; + ellipseData.width = Number(ellipseXML.@[ConstValues.A_WIDTH]); + ellipseData.height = Number(ellipseXML.@[ConstValues.A_HEIGHT]); + + parseTransform(ellipseXML[ConstValues.TRANSFORM][0], ellipseData.transform, ellipseData.pivot); + + return ellipseData; + } + + private static function parseSlotData(slotXML:XML):SlotData + { + var slotData:SlotData = new SlotData(); + slotData.name = slotXML.@[ConstValues.A_NAME]; + slotData.parent = slotXML.@[ConstValues.A_PARENT]; + slotData.zOrder = getNumber(slotXML,ConstValues.A_Z_ORDER,0)||0; + slotData.blendMode = slotXML.@[ConstValues.A_BLENDMODE]; + slotData.displayIndex = 0; + return slotData; + } + + private static function parseSkinData(skinXML:XML, data:DragonBonesData):SkinData + { + var skinData:SkinData = new SkinData(); + skinData.name = skinXML.@[ConstValues.A_NAME]; + + for each(var slotXML:XML in skinXML[ConstValues.SLOT]) + { + skinData.addSlotData(parseSkinSlotData(slotXML, data)); + } + + return skinData; + } + + private static function parseSkinSlotData(slotXML:XML, data:DragonBonesData):SlotData + { + var slotData:SlotData = new SlotData(); + slotData.name = slotXML.@[ConstValues.A_NAME]; + slotData.parent = slotXML.@[ConstValues.A_PARENT]; + slotData.zOrder = getNumber(slotXML, ConstValues.A_Z_ORDER, 0) || 0; + slotData.blendMode = slotXML.@[ConstValues.A_BLENDMODE]; + for each(var displayXML:XML in slotXML[ConstValues.DISPLAY]) + { + slotData.addDisplayData(parseDisplayData(displayXML, data)); + } + + return slotData; + } + + private static function parseDisplayData(displayXML:XML, data:DragonBonesData):DisplayData + { + var displayData:DisplayData = new DisplayData(); + displayData.name = displayXML.@[ConstValues.A_NAME]; + displayData.type = displayXML.@[ConstValues.A_TYPE]; + + displayData.pivot = new Point(); + //displayData.pivot = data.addSubTexturePivot( + //0, + //0, + //displayData.name + //); + + parseTransform(displayXML[ConstValues.TRANSFORM][0], displayData.transform, displayData.pivot); + + if (tempDragonBonesData) + { + tempDragonBonesData.addDisplayData(displayData); + } + return displayData; + } + + /** @private */ + dragonBones_internal static function parseAnimationData(animationXML:XML, armatureData:ArmatureData, frameRate:uint, isGlobalData:Boolean):AnimationData + { + var animationData:AnimationData = new AnimationData(); + animationData.name = animationXML.@[ConstValues.A_NAME]; + animationData.frameRate = frameRate; + animationData.duration = Math.round((int(animationXML.@[ConstValues.A_DURATION]) || 1) * 1000 / frameRate); + animationData.playTimes = int(getNumber(animationXML, ConstValues.A_LOOP, 1)); + animationData.fadeTime = getNumber(animationXML, ConstValues.A_FADE_IN_TIME, 0) || 0; + animationData.scale = getNumber(animationXML, ConstValues.A_SCALE, 1) || 0; + //use frame tweenEase, NaN + //overwrite frame tweenEase, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + animationData.tweenEasing = getNumber(animationXML, ConstValues.A_TWEEN_EASING, NaN); + animationData.autoTween = getBoolean(animationXML, ConstValues.A_AUTO_TWEEN, true); + + for each(var frameXML:XML in animationXML[ConstValues.FRAME]) + { + var frame:Frame = parseTransformFrame(frameXML, frameRate, isGlobalData); + animationData.addFrame(frame); + } + + parseTimeline(animationXML, animationData); + + var lastFrameDuration:int = animationData.duration; + for each(var timelineXML:XML in animationXML[ConstValues.TIMELINE]) + { + var timeline:TransformTimeline = parseTransformTimeline(timelineXML, animationData.duration, frameRate, isGlobalData); + lastFrameDuration = Math.min(lastFrameDuration, timeline.frameList[timeline.frameList.length - 1].duration); + animationData.addTimeline(timeline); + + var slotTimeline:SlotTimeline = parseSlotTimeline(timelineXML, animationData.duration, frameRate, isGlobalData); + if (slotTimeline.frameList.length > 0) + { + lastFrameDuration = Math.min(lastFrameDuration, slotTimeline.frameList[slotTimeline.frameList.length - 1].duration); + animationData.addSlotTimeline(slotTimeline); + } + } + + if(animationData.frameList.length > 0) + { + lastFrameDuration = Math.min(lastFrameDuration, animationData.frameList[animationData.frameList.length - 1].duration); + } + animationData.lastFrameDuration = lastFrameDuration; + + DBDataUtil.addHideTimeline(animationData, armatureData); + DBDataUtil.transformAnimationData(animationData, armatureData, isGlobalData); + + return animationData; + } + + private static function parseSlotTimeline(timelineXML:XML, duration:int, frameRate:uint, isGlobalData:Boolean):SlotTimeline + { + var timeline:SlotTimeline = new SlotTimeline(); + timeline.name = timelineXML.@[ConstValues.A_NAME]; + timeline.scale = getNumber(timelineXML, ConstValues.A_SCALE, 1) || 0; + timeline.offset = getNumber(timelineXML, ConstValues.A_OFFSET, 0) || 0; + timeline.duration = duration; + + for each(var frameXML:XML in timelineXML[ConstValues.FRAME]) + { + var frame:SlotFrame = parseSlotFrame(frameXML, frameRate, isGlobalData); + timeline.addFrame(frame); + } + + parseTimeline(timelineXML, timeline); + + return timeline; + } + + private static function parseSlotFrame(frameXML:XML, frameRate:uint, isGlobalData:Boolean):SlotFrame + { + var frame:SlotFrame = new SlotFrame(); + parseFrame(frameXML, frame, frameRate); + + frame.visible = !getBoolean(frameXML, ConstValues.A_HIDE, false); + + //NaN:no tween, 10:auto tween, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + frame.tweenEasing = getNumber(frameXML, ConstValues.A_TWEEN_EASING, 10); + frame.displayIndex = int(getNumber(frameXML,ConstValues.A_DISPLAY_INDEX,0)); + + //如果为NaN,则说明没有改变过zOrder + frame.zOrder = getNumber(frameXML, ConstValues.A_Z_ORDER, isGlobalData ? NaN:0); + + var colorTransformXML:XML = frameXML[ConstValues.COLOR_TRANSFORM][0]; + if(colorTransformXML) + { + frame.color = new ColorTransform(); + parseColorTransform(colorTransformXML, frame.color); + } + + return frame; + } + + private static function parseTransformTimeline(timelineXML:XML, duration:int, frameRate:uint, isGlobalData:Boolean):TransformTimeline + { + var timeline:TransformTimeline = new TransformTimeline(); + timeline.name = timelineXML.@[ConstValues.A_NAME]; + timeline.scale = getNumber(timelineXML, ConstValues.A_SCALE, 1) || 0; + timeline.offset = getNumber(timelineXML, ConstValues.A_OFFSET, 0) || 0; + timeline.originPivot.x = getNumber(timelineXML, ConstValues.A_PIVOT_X, 0) || 0; + timeline.originPivot.y = getNumber(timelineXML, ConstValues.A_PIVOT_Y, 0) || 0; + timeline.duration = duration; + + for each(var frameXML:XML in timelineXML[ConstValues.FRAME]) + { + var frame:TransformFrame = parseTransformFrame(frameXML, frameRate, isGlobalData); + timeline.addFrame(frame); + } + + parseTimeline(timelineXML, timeline); + + return timeline; + } + + private static function parseMainFrame(frameXML:XML, frameRate:uint):Frame + { + var frame:Frame = new Frame(); + parseFrame(frameXML, frame, frameRate); + return frame; + } + + private static function parseTransformFrame(frameXML:XML, frameRate:uint, isGlobalData:Boolean):TransformFrame + { + var frame:TransformFrame = new TransformFrame(); + parseFrame(frameXML, frame, frameRate); + + frame.visible = !getBoolean(frameXML, ConstValues.A_HIDE, false); + + //NaN:no tween, 10:auto tween, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + frame.tweenEasing = getNumber(frameXML, ConstValues.A_TWEEN_EASING, 10); + frame.tweenRotate = int(getNumber(frameXML, ConstValues.A_TWEEN_ROTATE,0)); + frame.tweenScale = getBoolean(frameXML, ConstValues.A_TWEEN_SCALE, true); + //frame.displayIndex = int(getNumber(frameXML, ConstValues.A_DISPLAY_INDEX, 0)); + + //如果为NaN,则说明没有改变过zOrder + //frame.zOrder = getNumber(frameXML, ConstValues.A_Z_ORDER, isGlobalData ? NaN : 0); + + parseTransform(frameXML[ConstValues.TRANSFORM][0], frame.transform, frame.pivot); + if(isGlobalData)//绝对数据 + { + frame.global.copy(frame.transform); + } + + frame.scaleOffset.x = getNumber(frameXML, ConstValues.A_SCALE_X_OFFSET, 0) || 0; + frame.scaleOffset.y = getNumber(frameXML, ConstValues.A_SCALE_Y_OFFSET, 0) || 0; + + //var colorTransformXML:XML = frameXML[ConstValues.COLOR_TRANSFORM][0]; + //if(colorTransformXML) + //{ + //frame.color = new ColorTransform(); + //parseColorTransform(colorTransformXML, frame.color); + //} + + return frame; + } + + private static function parseTimeline(timelineXML:XML, timeline:Timeline):void + { + var position:int = 0; + var frame:Frame; + for each(frame in timeline.frameList) + { + frame.position = position; + position += frame.duration; + } + if(frame) + { + frame.duration = timeline.duration - frame.position; + } + } + + private static function parseFrame(frameXML:XML, frame:Frame, frameRate:uint):void + { + frame.duration = Math.round((int(frameXML.@[ConstValues.A_DURATION]) || 1) * 1000 / frameRate); + frame.action = frameXML.@[ConstValues.A_ACTION]; + frame.event = frameXML.@[ConstValues.A_EVENT]; + frame.sound = frameXML.@[ConstValues.A_SOUND]; + } + + private static function parseTransform(transformXML:XML, transform:DBTransform, pivot:Point = null):void + { + if(transformXML) + { + if(transform) + { + transform.x = getNumber(transformXML, ConstValues.A_X, 0) || 0; + transform.y = getNumber(transformXML, ConstValues.A_Y, 0) || 0; + transform.skewX = getNumber(transformXML, ConstValues.A_SKEW_X, 0) * ConstValues.ANGLE_TO_RADIAN || 0; + transform.skewY = getNumber(transformXML, ConstValues.A_SKEW_Y, 0) * ConstValues.ANGLE_TO_RADIAN || 0; + transform.scaleX = getNumber(transformXML, ConstValues.A_SCALE_X, 1) || 0; + transform.scaleY = getNumber(transformXML, ConstValues.A_SCALE_Y, 1) || 0; + } + if(pivot) + { + pivot.x = getNumber(transformXML, ConstValues.A_PIVOT_X, 0) || 0; + pivot.y = getNumber(transformXML, ConstValues.A_PIVOT_Y, 0) || 0; + } + } + } + + private static function parseColorTransform(colorTransformXML:XML, colorTransform:ColorTransform):void + { + if(colorTransformXML) + { + if(colorTransform) + { + colorTransform.alphaOffset = int(colorTransformXML.@[ConstValues.A_ALPHA_OFFSET]); + colorTransform.redOffset = int(colorTransformXML.@[ConstValues.A_RED_OFFSET]); + colorTransform.greenOffset = int(colorTransformXML.@[ConstValues.A_GREEN_OFFSET]); + colorTransform.blueOffset = int(colorTransformXML.@[ConstValues.A_BLUE_OFFSET]); + + colorTransform.alphaMultiplier = int(getNumber(colorTransformXML, ConstValues.A_ALPHA_MULTIPLIER, 100) || 100) * 0.01; + colorTransform.redMultiplier = int(getNumber(colorTransformXML, ConstValues.A_RED_MULTIPLIER, 100) || 100) * 0.01; + colorTransform.greenMultiplier = int(getNumber(colorTransformXML, ConstValues.A_GREEN_MULTIPLIER, 100) || 100) * 0.01; + colorTransform.blueMultiplier = int(getNumber(colorTransformXML, ConstValues.A_BLUE_MULTIPLIER, 100) || 100) * 0.01; + } + } + } + + private static function getBoolean(data:XML, key:String, defaultValue:Boolean):Boolean + { + if(data && data.@[key].length() > 0) + { + switch(String(data.@[key])) + { + case "0": + case "NaN": + case "": + case "false": + case "null": + case "undefined": + return false; + + case "1": + case "true": + default: + return true; + } + } + return defaultValue; + } + + private static function getNumber(data:XML, key:String, defaultValue:Number):Number + { + if(data && data.@[key].length() > 0) + { + switch(String(data.@[key])) + { + case "NaN": + case "": + case "false": + case "null": + case "undefined": + return NaN; + + default: + return Number(data.@[key]); + } + } + return defaultValue; + } + } + +} \ No newline at end of file diff --git a/srclib/dragonBones/objects/XMLDataParser.as b/srclib/dragonBones/objects/XMLDataParser.as new file mode 100644 index 00000000..1e661a3d --- /dev/null +++ b/srclib/dragonBones/objects/XMLDataParser.as @@ -0,0 +1,476 @@ +package dragonBones.objects { + + import dragonBones.core.DragonBones; + import dragonBones.core.dragonBones_internal; + import dragonBones.textures.TextureData; + import dragonBones.utils.ConstValues; + import dragonBones.utils.DBDataUtil; + + import flash.geom.ColorTransform; + import flash.geom.Point; + import flash.geom.Rectangle; + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0, Flash 10 + * @langversion 3.0 + * @version 2.0 + */ + + + use namespace dragonBones_internal; + + /** + * The XMLDataParser class parses xml data from dragonBones generated maps. + */ + final public class XMLDataParser + { + private static var tempDragonBonesData:DragonBonesData; + + public static function parseTextureAtlasData(rawData:XML, scale:Number = 1):Object + { + var textureAtlasData:Object = {}; + textureAtlasData.__name = rawData.@[ConstValues.A_NAME]; + var subTextureFrame:Rectangle; + for each (var subTextureXML:XML in rawData[ConstValues.SUB_TEXTURE]) + { + var subTextureName:String = subTextureXML.@[ConstValues.A_NAME]; + + var subTextureRegion:Rectangle = new Rectangle(); + subTextureRegion.x = int(subTextureXML.@[ConstValues.A_X]) / scale; + subTextureRegion.y = int(subTextureXML.@[ConstValues.A_Y]) / scale; + subTextureRegion.width = int(subTextureXML.@[ConstValues.A_WIDTH]) / scale; + subTextureRegion.height = int(subTextureXML.@[ConstValues.A_HEIGHT]) / scale; + var rotated:Boolean = subTextureXML.@[ConstValues.A_ROTATED] == "true"; + + var frameWidth:Number = int(subTextureXML.@[ConstValues.A_FRAME_WIDTH]) / scale; + var frameHeight:Number = int(subTextureXML.@[ConstValues.A_FRAME_HEIGHT]) / scale; + + if(frameWidth > 0 && frameHeight > 0) + { + subTextureFrame = new Rectangle(); + subTextureFrame.x = int(subTextureXML.@[ConstValues.A_FRAME_X]) / scale; + subTextureFrame.y = int(subTextureXML.@[ConstValues.A_FRAME_Y]) / scale; + subTextureFrame.width = frameWidth; + subTextureFrame.height = frameHeight; + } + else + { + subTextureFrame = null; + } + + textureAtlasData[subTextureName] = new TextureData(subTextureRegion, subTextureFrame, rotated); + } + + return textureAtlasData; + } + + /** + * Parse the SkeletonData. + * @param xml The SkeletonData xml to parse. + * @return A SkeletonData instance. + */ + public static function parseDragonBonesData(rawData:XML):DragonBonesData + { + if(!rawData) + { + throw new ArgumentError(); + } + var version:String = rawData.@[ConstValues.A_VERSION]; + switch (version) + { + case "2.3": + case "3.0": + return XML3DataParser.parseSkeletonData(rawData); + break; + case DragonBones.DATA_VERSION: + break; + + default: + throw new Error("Nonsupport version!"); + } + + var frameRate:uint = int(rawData.@[ConstValues.A_FRAME_RATE]); + + var outputDragonBonesData:DragonBonesData = new DragonBonesData(); + outputDragonBonesData.name = rawData.@[ConstValues.A_NAME]; + outputDragonBonesData.isGlobalData = rawData.@[ConstValues.A_IS_GLOBAL] == "0" ? false : true; + tempDragonBonesData = outputDragonBonesData; + for each(var armatureXML:XML in rawData[ConstValues.ARMATURE]) + { + outputDragonBonesData.addArmatureData(parseArmatureData(armatureXML, frameRate)); + } + tempDragonBonesData = null; + + return outputDragonBonesData; + } + + private static function parseArmatureData(armatureXML:XML, frameRate:uint):ArmatureData + { + var outputArmatureData:ArmatureData = new ArmatureData(); + outputArmatureData.name = armatureXML.@[ConstValues.A_NAME]; + + for each(var boneXML:XML in armatureXML[ConstValues.BONE]) + { + outputArmatureData.addBoneData(parseBoneData(boneXML)); + } + for each(var slotXML:XML in armatureXML[ConstValues.SLOT]) + { + outputArmatureData.addSlotData(parseSlotData(slotXML)); + } + for each(var skinXML:XML in armatureXML[ConstValues.SKIN]) + { + outputArmatureData.addSkinData(parseSkinData(skinXML)); + } + + if(tempDragonBonesData.isGlobalData) + { + DBDataUtil.transformArmatureData(outputArmatureData); + } + + outputArmatureData.sortBoneDataList(); + + var animationXML:XML; + + for each(animationXML in armatureXML[ConstValues.ANIMATION]) + { + var animationData:AnimationData = parseAnimationData(animationXML, frameRate); + DBDataUtil.addHideTimeline(animationData, outputArmatureData); + DBDataUtil.transformAnimationData(animationData, outputArmatureData, tempDragonBonesData.isGlobalData); + outputArmatureData.addAnimationData(animationData); + } + + return outputArmatureData; + } + + private static function parseBoneData(boneXML:XML):BoneData + { + var boneData:BoneData = new BoneData(); + boneData.name = boneXML.@[ConstValues.A_NAME]; + boneData.parent = boneXML.@[ConstValues.A_PARENT]; + boneData.length = Number(boneXML.@[ConstValues.A_LENGTH]); + boneData.inheritRotation = getBoolean(boneXML, ConstValues.A_INHERIT_ROTATION, true); + boneData.inheritScale = getBoolean(boneXML, ConstValues.A_INHERIT_SCALE, true); + + parseTransform(boneXML[ConstValues.TRANSFORM][0], boneData.transform); + if(tempDragonBonesData.isGlobalData)//绝对数据 + { + boneData.global.copy(boneData.transform); + } + + return boneData; + } + + + private static function parseSkinData(skinXML:XML):SkinData + { + var skinData:SkinData = new SkinData(); + skinData.name = skinXML.@[ConstValues.A_NAME]; + + for each(var slotXML:XML in skinXML[ConstValues.SLOT]) + { + skinData.addSlotData(parseSlotDisplayData(slotXML)); + } + + return skinData; + } + + private static function parseSlotDisplayData(slotXML:XML):SlotData + { + var slotData:SlotData = new SlotData(); + slotData.name = slotXML.@[ConstValues.A_NAME]; + for each(var displayXML:XML in slotXML[ConstValues.DISPLAY]) + { + slotData.addDisplayData(parseDisplayData(displayXML)); + } + + return slotData; + } + + private static function parseSlotData(slotXML:XML):SlotData + { + var slotData:SlotData = new SlotData(); + slotData.name = slotXML.@[ConstValues.A_NAME]; + slotData.parent = slotXML.@[ConstValues.A_PARENT]; + slotData.zOrder = getNumber(slotXML,ConstValues.A_Z_ORDER,0)||0; + slotData.blendMode = slotXML.@[ConstValues.A_BLENDMODE]; + slotData.displayIndex = slotXML.@[ConstValues.A_DISPLAY_INDEX]; + return slotData; + } + + private static function parseDisplayData(displayXML:XML):DisplayData + { + var displayData:DisplayData = new DisplayData(); + displayData.name = displayXML.@[ConstValues.A_NAME]; + displayData.type = displayXML.@[ConstValues.A_TYPE]; + + parseTransform(displayXML[ConstValues.TRANSFORM][0], displayData.transform, displayData.pivot); + + displayData.pivot.x = NaN; + displayData.pivot.y = NaN; + + if(tempDragonBonesData!=null) + { + tempDragonBonesData.addDisplayData(displayData); + } + + return displayData; + } + + /** @private */ + dragonBones_internal static function parseAnimationData(animationXML:XML, frameRate:uint):AnimationData + { + var animationData:AnimationData = new AnimationData(); + animationData.name = animationXML.@[ConstValues.A_NAME]; + animationData.frameRate = frameRate; + animationData.duration = Math.round((int(animationXML.@[ConstValues.A_DURATION]) || 1) * 1000 / frameRate); + animationData.playTimes = int(getNumber(animationXML,ConstValues.A_PLAY_TIMES,1)); + animationData.fadeTime = getNumber(animationXML,ConstValues.A_FADE_IN_TIME,0)||0; + animationData.scale = getNumber(animationXML, ConstValues.A_SCALE, 1) || 0; + //use frame tweenEase, NaN + //overwrite frame tweenEase, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + animationData.tweenEasing = getNumber(animationXML, ConstValues.A_TWEEN_EASING, NaN); + animationData.autoTween = getBoolean(animationXML, ConstValues.A_AUTO_TWEEN, true); + + for each(var frameXML:XML in animationXML[ConstValues.FRAME]) + { + var frame:Frame = parseTransformFrame(frameXML, frameRate); + animationData.addFrame(frame); + } + + parseTimeline(animationXML, animationData); + + var lastFrameDuration:int = animationData.duration; + for each(var timelineXML:XML in animationXML[ConstValues.BONE]) + { + var timeline:TransformTimeline = parseTransformTimeline(timelineXML, animationData.duration, frameRate); + if (timeline.frameList.length > 0) + { + lastFrameDuration = Math.min(lastFrameDuration, timeline.frameList[timeline.frameList.length - 1].duration); + animationData.addTimeline(timeline); + } + + } + + for each(var slotTimelineXML:XML in animationXML[ConstValues.SLOT]) + { + var slotTimeline:SlotTimeline = parseSlotTimeline(slotTimelineXML, animationData.duration, frameRate); + if (slotTimeline.frameList.length > 0) + { + lastFrameDuration = Math.min(lastFrameDuration, slotTimeline.frameList[slotTimeline.frameList.length - 1].duration); + animationData.addSlotTimeline(slotTimeline); + } + } + + if(animationData.frameList.length > 0) + { + lastFrameDuration = Math.min(lastFrameDuration, animationData.frameList[animationData.frameList.length - 1].duration); + } + animationData.lastFrameDuration = lastFrameDuration; + + return animationData; + } + + private static function parseTransformTimeline(timelineXML:XML, duration:int, frameRate:uint):TransformTimeline + { + var timeline:TransformTimeline = new TransformTimeline(); + timeline.name = timelineXML.@[ConstValues.A_NAME]; + timeline.scale = getNumber(timelineXML, ConstValues.A_SCALE, 1) || 0; + timeline.offset = getNumber(timelineXML, ConstValues.A_OFFSET, 0) || 0; + timeline.originPivot.x = getNumber(timelineXML, ConstValues.A_PIVOT_X, 0) || 0; + timeline.originPivot.y = getNumber(timelineXML, ConstValues.A_PIVOT_Y, 0) || 0; + timeline.duration = duration; + + for each(var frameXML:XML in timelineXML[ConstValues.FRAME]) + { + var frame:TransformFrame = parseTransformFrame(frameXML, frameRate); + timeline.addFrame(frame); + } + + parseTimeline(timelineXML, timeline); + + return timeline; + } + + private static function parseSlotTimeline(timelineXML:XML, duration:int, frameRate:uint):SlotTimeline + { + var timeline:SlotTimeline = new SlotTimeline(); + timeline.name = timelineXML.@[ConstValues.A_NAME]; + timeline.scale = getNumber(timelineXML, ConstValues.A_SCALE, 1) || 0; + timeline.offset = getNumber(timelineXML, ConstValues.A_OFFSET, 0) || 0; + timeline.duration = duration; + + for each(var frameXML:XML in timelineXML[ConstValues.FRAME]) + { + var frame:SlotFrame = parseSlotFrame(frameXML, frameRate); + timeline.addFrame(frame); + } + + parseTimeline(timelineXML, timeline); + + return timeline; + } + + private static function parseMainFrame(frameXML:XML, frameRate:uint):Frame + { + var frame:Frame = new Frame(); + parseFrame(frameXML, frame, frameRate); + return frame; + } + + private static function parseSlotFrame(frameXML:XML, frameRate:uint):SlotFrame + { + var frame:SlotFrame = new SlotFrame(); + parseFrame(frameXML, frame, frameRate); + + frame.visible = !getBoolean(frameXML, ConstValues.A_HIDE, false); + + //NaN:no tween, 10:auto tween, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + frame.tweenEasing = getNumber(frameXML, ConstValues.A_TWEEN_EASING, 10); + frame.displayIndex = int(getNumber(frameXML,ConstValues.A_DISPLAY_INDEX,0)); + + //如果为NaN,则说明没有改变过zOrder + frame.zOrder = getNumber(frameXML, ConstValues.A_Z_ORDER, tempDragonBonesData.isGlobalData ? NaN:0); + + var colorTransformXML:XML = frameXML[ConstValues.COLOR][0]; + if(colorTransformXML) + { + frame.color = new ColorTransform(); + parseColorTransform(colorTransformXML, frame.color); + } + + return frame; + } + + private static function parseTransformFrame(frameXML:XML, frameRate:uint):TransformFrame + { + var frame:TransformFrame = new TransformFrame(); + parseFrame(frameXML, frame, frameRate); + + frame.visible = !getBoolean(frameXML, ConstValues.A_HIDE, false); + + //NaN:no tween, 10:auto tween, [-1, 0):ease in, 0:line easing, (0, 1]:ease out, (1, 2]:ease in out + frame.tweenEasing = getNumber(frameXML, ConstValues.A_TWEEN_EASING, 10); + frame.tweenRotate = int(getNumber(frameXML,ConstValues.A_TWEEN_ROTATE,0)); + frame.tweenScale = getBoolean(frameXML, ConstValues.A_TWEEN_SCALE, true); +// frame.displayIndex = int(getNumber(frameXML,ConstValues.A_DISPLAY_INDEX,0)); + + + parseTransform(frameXML[ConstValues.TRANSFORM][0], frame.transform, frame.pivot); + if(tempDragonBonesData.isGlobalData)//绝对数据 + { + frame.global.copy(frame.transform); + } + + frame.scaleOffset.x = getNumber(frameXML, ConstValues.A_SCALE_X_OFFSET, 0) || 0; + frame.scaleOffset.y = getNumber(frameXML, ConstValues.A_SCALE_Y_OFFSET, 0) || 0; + + return frame; + } + + private static function parseTimeline(timelineXML:XML, timeline:Timeline):void + { + var position:int = 0; + var frame:Frame; + for each(frame in timeline.frameList) + { + frame.position = position; + position += frame.duration; + } + if(frame) + { + frame.duration = timeline.duration - frame.position; + } + } + + private static function parseFrame(frameXML:XML, frame:Frame, frameRate:uint):void + { + frame.duration = Math.round((int(frameXML.@[ConstValues.A_DURATION])) * 1000 / frameRate); + frame.action = frameXML.@[ConstValues.A_ACTION]; + frame.event = frameXML.@[ConstValues.A_EVENT]; + frame.sound = frameXML.@[ConstValues.A_SOUND]; + } + + private static function parseTransform(transformXML:XML, transform:DBTransform, pivot:Point = null):void + { + if(transformXML) + { + if(transform) + { + transform.x = getNumber(transformXML,ConstValues.A_X,0) || 0; + transform.y = getNumber(transformXML,ConstValues.A_Y,0) || 0; + transform.skewX = getNumber(transformXML,ConstValues.A_SKEW_X,0) * ConstValues.ANGLE_TO_RADIAN || 0; + transform.skewY = getNumber(transformXML,ConstValues.A_SKEW_Y,0) * ConstValues.ANGLE_TO_RADIAN || 0; + transform.scaleX = getNumber(transformXML, ConstValues.A_SCALE_X, 1) || 0; + transform.scaleY = getNumber(transformXML, ConstValues.A_SCALE_Y, 1) || 0; + } + if(pivot) + { + pivot.x = getNumber(transformXML,ConstValues.A_PIVOT_X,0) || 0; + pivot.y = getNumber(transformXML,ConstValues.A_PIVOT_Y,0) || 0; + } + } + } + + private static function parseColorTransform(colorTransformXML:XML, colorTransform:ColorTransform):void + { + if(colorTransformXML) + { + if(colorTransform) + { + colorTransform.alphaOffset = int(colorTransformXML.@[ConstValues.A_ALPHA_OFFSET]); + colorTransform.redOffset = int(colorTransformXML.@[ConstValues.A_RED_OFFSET]); + colorTransform.greenOffset = int(colorTransformXML.@[ConstValues.A_GREEN_OFFSET]); + colorTransform.blueOffset = int(colorTransformXML.@[ConstValues.A_BLUE_OFFSET]); + + colorTransform.alphaMultiplier = int(getNumber(colorTransformXML, ConstValues.A_ALPHA_MULTIPLIER, 100) || 0) * 0.01; + colorTransform.redMultiplier = int(getNumber(colorTransformXML, ConstValues.A_RED_MULTIPLIER, 100) || 0) * 0.01; + colorTransform.greenMultiplier = int(getNumber(colorTransformXML, ConstValues.A_GREEN_MULTIPLIER, 100) || 0) * 0.01; + colorTransform.blueMultiplier = int(getNumber(colorTransformXML, ConstValues.A_BLUE_MULTIPLIER, 100) || 0) * 0.01; + } + } + } + + private static function getBoolean(data:XML, key:String, defaultValue:Boolean):Boolean + { + if(data && data.@[key].length() > 0) + { + switch(String(data.@[key])) + { + case "0": + case "NaN": + case "": + case "false": + case "null": + case "undefined": + return false; + + case "1": + case "true": + default: + return true; + } + } + return defaultValue; + } + + private static function getNumber(data:XML, key:String, defaultValue:Number):Number + { + if(data && data.@[key].length() > 0) + { + switch(String(data.@[key])) + { + case "NaN": + case "": + case "false": + case "null": + case "undefined": + return NaN; + + default: + return Number(data.@[key]); + } + } + return defaultValue; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/textures/ITextureAtlas.as b/srclib/dragonBones/textures/ITextureAtlas.as new file mode 100644 index 00000000..495d3aa0 --- /dev/null +++ b/srclib/dragonBones/textures/ITextureAtlas.as @@ -0,0 +1,31 @@ +package dragonBones.textures { + + import flash.geom.Rectangle; + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0 + * @langversion 3.0 + * @version 2.0 + */ + /** + * The ITextureAtlas interface defines the methods used by all ITextureAtlas within the dragonBones system (flash or starling DisplayObject based). + * @see dragonBones.Armature + */ + public interface ITextureAtlas + { + /** + * The name of this ITextureAtlas. + */ + function get name():String; + /** + * Clean up resources. + */ + function dispose():void; + /** + * Get the specific region of the TextureAtlas occupied by assets defined by that name. + * @param name The name of the assets represented by that name. + * @return Rectangle The rectangle area occupied by those assets. + */ + function getRegion(name:String):Rectangle + } +} \ No newline at end of file diff --git a/srclib/dragonBones/textures/NativeTextureAtlas.as b/srclib/dragonBones/textures/NativeTextureAtlas.as new file mode 100644 index 00000000..bae1d78e --- /dev/null +++ b/srclib/dragonBones/textures/NativeTextureAtlas.as @@ -0,0 +1,154 @@ +package dragonBones.textures { + + import dragonBones.core.dragonBones_internal; + import dragonBones.objects.DataParser; + + import flash.display.BitmapData; + import flash.display.MovieClip; + import flash.geom.Rectangle; + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0, Flash 10 + * @langversion 3.0 + * @version 2.0 + */ + + use namespace dragonBones_internal; + + /** + * The NativeTextureAtlas creates and manipulates TextureAtlas from traditional flash.display.DisplayObject. + */ + public class NativeTextureAtlas implements ITextureAtlas + { + /** + * @private + */ + protected var _subTextureDataDic:Object; + /** + * @private + */ + protected var _isDifferentConfig:Boolean; + /** + * @private + */ + protected var _name:String; + /** + * The name of this NativeTextureAtlas instance. + */ + public function get name():String + { + return _name; + } + + protected var _movieClip:MovieClip; + /** + * The MovieClip created by this NativeTextureAtlas instance. + */ + public function get movieClip():MovieClip + { + return _movieClip; + } + + protected var _bitmapData:BitmapData; + /** + * The BitmapData created by this NativeTextureAtlas instance. + */ + public function get bitmapData():BitmapData + { + return _bitmapData; + } + + protected var _scale:Number; + /** + * @private + */ + public function get scale():Number + { + return _scale; + } + /** + * Creates a new NativeTextureAtlas instance. + * @param texture A MovieClip or Bitmap. + * @param textureAtlasRawData The textureAtlas config data. + * @param textureScale A scale value (x and y axis) + * @param isDifferentConfig + */ + public function NativeTextureAtlas(texture:Object, textureAtlasRawData:Object, textureScale:Number = 1, isDifferentConfig:Boolean = false) + { + _scale = textureScale; + _isDifferentConfig = isDifferentConfig; + if (texture is BitmapData) + { + _bitmapData = texture as BitmapData; + } + else if (texture is MovieClip) + { + _movieClip = texture as MovieClip; + _movieClip.stop(); + } + parseData(textureAtlasRawData); + } + /** + * Clean up all resources used by this NativeTextureAtlas instance. + */ + public function dispose():void + { + _movieClip = null; + if (_bitmapData) + { + _bitmapData.dispose(); + } + _bitmapData = null; + } + /** + * The area occupied by all assets related to that name. + * @param name The name of these assets. + * @return Rectangle The area occupied by all assets related to that name. + */ + public function getRegion(name:String):Rectangle + { + var textureData:TextureData = _subTextureDataDic[name] as TextureData; + if(textureData) + { + return textureData.region; + } + + return null; + } + + public function getFrame(name:String):Rectangle + { + var textureData:TextureData = _subTextureDataDic[name] as TextureData; + if(textureData) + { + return textureData.frame; + } + + return null; + } + + protected function parseData(textureAtlasRawData:Object):void + { + _subTextureDataDic = DataParser.parseTextureAtlasData(textureAtlasRawData, _isDifferentConfig ? _scale : 1); + _name = _subTextureDataDic.__name; + + delete _subTextureDataDic.__name; + } + + dragonBones_internal function movieClipToBitmapData():void + { + if (!_bitmapData && _movieClip) + { + _movieClip.gotoAndStop(1); + _bitmapData = new BitmapData(getNearest2N(_movieClip.width), getNearest2N(_movieClip.height), true, 0xFF00FF); + _bitmapData.draw(_movieClip); + _movieClip.gotoAndStop(_movieClip.totalFrames); + } + } + + private function getNearest2N(_n:uint):uint + { + return _n & _n - 1?1 << _n.toString(2).length:_n; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/textures/StarlingTextureAtlas.as b/srclib/dragonBones/textures/StarlingTextureAtlas.as new file mode 100644 index 00000000..7cef68f0 --- /dev/null +++ b/srclib/dragonBones/textures/StarlingTextureAtlas.as @@ -0,0 +1,118 @@ +package dragonBones.textures { + + import dragonBones.core.dragonBones_internal; + import dragonBones.objects.DataParser; + + import starling.textures.SubTexture; + import starling.textures.Texture; + import starling.textures.TextureAtlas; + + import flash.display.BitmapData; + /** + * Copyright 2012-2013. DragonBones. All Rights Reserved. + * @playerversion Flash 10.0, Flash 10 + * @langversion 3.0 + * @version 2.0 + */ + use namespace dragonBones_internal; + + /** + * The StarlingTextureAtlas creates and manipulates TextureAtlas from starling.display.DisplayObject. + */ + public class StarlingTextureAtlas extends TextureAtlas implements ITextureAtlas + { + dragonBones_internal var _bitmapData:BitmapData; + /** + * @private + */ + protected var _subTextureDic:Object; + /** + * @private + */ + protected var _isDifferentConfig:Boolean; + /** + * @private + */ + protected var _scale:Number; + /** + * @private + */ + protected var _name:String; + /** + * The name of this StarlingTextureAtlas instance. + */ + public function get name():String + { + return _name; + } + /** + * Creates a new StarlingTextureAtlas instance. + * @param texture A texture instance. + * @param textureAtlasRawData A textureAtlas config data + * @param isDifferentXML + */ + public function StarlingTextureAtlas(texture:Texture, textureAtlasRawData:Object, isDifferentConfig:Boolean = false) + { + super(texture, null); + if (texture) + { + _scale = texture.scale; + _isDifferentConfig = isDifferentConfig; + } + _subTextureDic = {}; + parseData(textureAtlasRawData); + } + /** + * Clean up all resources used by this StarlingTextureAtlas instance. + */ + override public function dispose():void + { + super.dispose(); + for each (var subTexture:SubTexture in _subTextureDic) + { + subTexture.dispose(); + } + _subTextureDic = null; + + if (_bitmapData) + { + _bitmapData.dispose(); + } + _bitmapData = null; + } + + /** + * Get the Texture with that name. + * @param name The name ofthe Texture instance. + * @return The Texture instance. + */ + override public function getTexture(name:String):Texture + { + var texture:Texture = _subTextureDic[name]; + if (!texture) + { + texture = super.getTexture(name); + if (texture) + { + _subTextureDic[name] = texture; + } + } + return texture; + } + /** + * @private + */ + protected function parseData(textureAtlasRawData:Object):void + { + var textureAtlasData:Object = DataParser.parseTextureAtlasData(textureAtlasRawData, _isDifferentConfig ? _scale : 1); + _name = textureAtlasData.__name; + delete textureAtlasData.__name; + for(var subTextureName:String in textureAtlasData) + { + var textureData:TextureData = textureAtlasData[subTextureName]; + //, textureData.rotated + this.addRegion(subTextureName, textureData.region, textureData.frame); + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/textures/TextureData.as b/srclib/dragonBones/textures/TextureData.as new file mode 100644 index 00000000..9abdcee6 --- /dev/null +++ b/srclib/dragonBones/textures/TextureData.as @@ -0,0 +1,18 @@ +package dragonBones.textures { + + import flash.geom.Rectangle; + + public final class TextureData + { + public var region:Rectangle; + public var frame:Rectangle; + public var rotated:Boolean; + + public function TextureData(region:Rectangle, frame:Rectangle, rotated:Boolean) + { + this.region = region; + this.frame = frame; + this.rotated = rotated; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/utils/BytesType.as b/srclib/dragonBones/utils/BytesType.as new file mode 100644 index 00000000..36861f8b --- /dev/null +++ b/srclib/dragonBones/utils/BytesType.as @@ -0,0 +1,46 @@ +package dragonBones.utils { + + import flash.utils.ByteArray; + + /** @private */ + public class BytesType + { + public static const SWF:String = "swf"; + public static const PNG:String = "png"; + public static const JPG:String = "jpg"; + public static const ATF:String = "atf"; + public static const ZIP:String = "zip"; + + public static function getType(bytes:ByteArray):String + { + var outputType:String; + var b1:uint = bytes[0]; + var b2:uint = bytes[1]; + var b3:uint = bytes[2]; + var b4:uint = bytes[3]; + if ((b1 == 0x46 || b1 == 0x43 || b1 == 0x5A) && b2 == 0x57 && b3 == 0x53) + { + //CWS FWS ZWS + outputType = SWF; + } + else if (b1 == 0x89 && b2 == 0x50 && b3 == 0x4E && b4 == 0x47) + { + //89 50 4e 47 0d 0a 1a 0a + outputType = PNG; + } + else if (b1 == 0xFF) + { + outputType = JPG; + } + else if (b1 == 0x41 && b2 == 0x54 && b3 == 0x46) + { + outputType = ATF; + } + else if (b1 == 0x50 && b2 == 0x4B) + { + outputType = ZIP; + } + return outputType; + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/utils/ColorTransformUtil.as b/srclib/dragonBones/utils/ColorTransformUtil.as new file mode 100644 index 00000000..8b5d6763 --- /dev/null +++ b/srclib/dragonBones/utils/ColorTransformUtil.as @@ -0,0 +1,43 @@ +package dragonBones.utils { + + import flash.geom.ColorTransform; + + public class ColorTransformUtil + { + static public var originalColor:ColorTransform = new ColorTransform(); + + public static function cloneColor(color:ColorTransform):ColorTransform + { + return new ColorTransform(color.redMultiplier,color.greenMultiplier,color.blueMultiplier,color.alphaMultiplier,color.redOffset,color.greenOffset,color.blueOffset,color.alphaOffset) + } + + public static function isEqual(color1:ColorTransform, color2:ColorTransform):Boolean + { + return color1.alphaOffset == color2.alphaOffset && + color1.redOffset == color2.redOffset && + color1.greenOffset == color2.greenOffset && + color1.blueOffset == color2.blueOffset && + color1.alphaMultiplier == color2.alphaMultiplier && + color1.redMultiplier == color2.redMultiplier && + color1.greenMultiplier == color2.greenMultiplier && + color1.blueMultiplier == color2.blueMultiplier; + } + + public static function minus(color1:ColorTransform, color2:ColorTransform, outputColor:ColorTransform):void + { + outputColor.alphaOffset = color1.alphaOffset - color2.alphaOffset; + outputColor.redOffset = color1.redOffset - color2.redOffset; + outputColor.greenOffset = color1.greenOffset - color2.greenOffset; + outputColor.blueOffset = color1.blueOffset - color2.blueOffset; + + outputColor.alphaMultiplier = color1.alphaMultiplier - color2.alphaMultiplier; + outputColor.redMultiplier = color1.redMultiplier - color2.redMultiplier; + outputColor.greenMultiplier = color1.greenMultiplier - color2.greenMultiplier; + outputColor.blueMultiplier = color1.blueMultiplier - color2.blueMultiplier; + } + + public function ColorTransformUtil() + { + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/utils/ConstValues.as b/srclib/dragonBones/utils/ConstValues.as new file mode 100644 index 00000000..6fa97375 --- /dev/null +++ b/srclib/dragonBones/utils/ConstValues.as @@ -0,0 +1,89 @@ +package dragonBones.utils +{ + + /** @private */ + final public class ConstValues + { + public static const ANGLE_TO_RADIAN:Number = Math.PI / 180; + public static const RADIAN_TO_ANGLE:Number = 180 / Math.PI; + + public static const DRAGON_BONES:String = "dragonBones"; + public static const ARMATURE:String = "armature"; + public static const SKIN:String = "skin"; + public static const BONE:String = "bone"; + public static const SLOT:String = "slot"; + public static const DISPLAY:String = "display"; + public static const ANIMATION:String = "animation"; + public static const TIMELINE:String = "timeline"; + public static const FRAME:String = "frame"; + public static const TRANSFORM:String = "transform"; + public static const COLOR_TRANSFORM:String = "colorTransform"; + public static const COLOR:String = "color"; + public static const RECTANGLE:String = "rectangle"; + public static const ELLIPSE:String = "ellipse"; + + public static const TEXTURE_ATLAS:String = "TextureAtlas"; + public static const SUB_TEXTURE:String = "SubTexture"; + + public static const A_ROTATED:String = "rotated"; + public static const A_FRAME_X:String = "frameX"; + public static const A_FRAME_Y:String = "frameY"; + public static const A_FRAME_WIDTH:String = "frameWidth"; + public static const A_FRAME_HEIGHT:String = "frameHeight"; + + public static const A_VERSION:String = "version"; + public static const A_IMAGE_PATH:String = "imagePath"; + public static const A_FRAME_RATE:String = "frameRate"; + public static const A_NAME:String = "name"; + public static const A_IS_GLOBAL:String = "isGlobal"; + public static const A_PARENT:String = "parent"; + public static const A_LENGTH:String = "length"; + public static const A_TYPE:String = "type"; + public static const A_FADE_IN_TIME:String = "fadeInTime"; + public static const A_DURATION:String = "duration"; + public static const A_SCALE:String = "scale"; + public static const A_OFFSET:String = "offset"; + public static const A_LOOP:String = "loop"; + public static const A_PLAY_TIMES:String = "playTimes"; + public static const A_EVENT:String = "event"; + public static const A_EVENT_PARAMETERS:String = "eventParameters"; + public static const A_SOUND:String = "sound"; + public static const A_ACTION:String = "action"; + public static const A_HIDE:String = "hide"; + public static const A_AUTO_TWEEN:String ="autoTween"; + public static const A_TWEEN_EASING:String = "tweenEasing"; + public static const A_TWEEN_ROTATE:String = "tweenRotate"; + public static const A_TWEEN_SCALE:String = "tweenScale"; + public static const A_DISPLAY_INDEX:String = "displayIndex"; + public static const A_Z_ORDER:String = "z"; + public static const A_BLENDMODE:String = "blendMode"; + public static const A_WIDTH:String = "width"; + public static const A_HEIGHT:String = "height"; + public static const A_INHERIT_SCALE:String = "inheritScale"; + public static const A_INHERIT_ROTATION:String = "inheritRotation"; + public static const A_X:String = "x"; + public static const A_Y:String = "y"; + public static const A_SKEW_X:String = "skX"; + public static const A_SKEW_Y:String = "skY"; + public static const A_SCALE_X:String = "scX"; + public static const A_SCALE_Y:String = "scY"; + public static const A_PIVOT_X:String = "pX"; + public static const A_PIVOT_Y:String = "pY"; + public static const A_ALPHA_OFFSET:String = "aO"; + public static const A_RED_OFFSET:String = "rO"; + public static const A_GREEN_OFFSET:String = "gO"; + public static const A_BLUE_OFFSET:String = "bO"; + public static const A_ALPHA_MULTIPLIER:String = "aM"; + public static const A_RED_MULTIPLIER:String = "rM"; + public static const A_GREEN_MULTIPLIER:String = "gM"; + public static const A_BLUE_MULTIPLIER:String = "bM"; + public static const A_CURVE:String = "curve"; + + public static const A_SCALE_X_OFFSET:String = "scXOffset"; + public static const A_SCALE_Y_OFFSET:String = "scYOffset"; + + public static const A_SCALE_MODE:String = "scaleMode"; + public static const A_FIXED_ROTATION:String = "fixedRotation"; + } + +} \ No newline at end of file diff --git a/srclib/dragonBones/utils/DBDataUtil.as b/srclib/dragonBones/utils/DBDataUtil.as new file mode 100644 index 00000000..ef2217ad --- /dev/null +++ b/srclib/dragonBones/utils/DBDataUtil.as @@ -0,0 +1,357 @@ +package dragonBones.utils { + + import dragonBones.objects.AnimationData; + import dragonBones.objects.ArmatureData; + import dragonBones.objects.BoneData; + import dragonBones.objects.DBTransform; + import dragonBones.objects.Frame; + import dragonBones.objects.SkinData; + import dragonBones.objects.SlotData; + import dragonBones.objects.SlotFrame; + import dragonBones.objects.SlotTimeline; + import dragonBones.objects.TransformFrame; + import dragonBones.objects.TransformTimeline; + + import flash.geom.Matrix; + import flash.geom.Point; + + /** @private */ + public final class DBDataUtil + { + public static function transformArmatureData(armatureData:ArmatureData):void + { + var boneDataList:Vector. = armatureData.boneDataList; + var i:int = boneDataList.length; + + while(i --) + { + var boneData:BoneData = boneDataList[i]; + if(boneData.parent) + { + var parentBoneData:BoneData = armatureData.getBoneData(boneData.parent); + if(parentBoneData) + { + boneData.transform.copy(boneData.global); + boneData.transform.divParent(parentBoneData.global); +// TransformUtil.globalToLocal(boneData.transform, parentBoneData.global); + } + } + } + } + + public static function transformArmatureDataAnimations(armatureData:ArmatureData):void + { + var animationDataList:Vector. = armatureData.animationDataList; + var i:int = animationDataList.length; + while(i --) + { + transformAnimationData(animationDataList[i], armatureData, false); + } + } + + public static function transformRelativeAnimationData(animationData:AnimationData, armatureData:ArmatureData):void + { + + } + + public static function transformAnimationData(animationData:AnimationData, armatureData:ArmatureData, isGlobalData:Boolean):void + { + if(!isGlobalData) + { + transformRelativeAnimationData(animationData, armatureData); + return; + } + + var skinData:SkinData = armatureData.getSkinData(null); + var boneDataList:Vector. = armatureData.boneDataList; + var slotDataList:Vector.; + if(skinData) + { + slotDataList = armatureData.slotDataList; + } + + for(var i:int = 0;i < boneDataList.length;i ++) + { + var boneData:BoneData = boneDataList[i]; + //绝对数据是不可能有slotTimeline的 + var timeline:TransformTimeline = animationData.getTimeline(boneData.name); + var slotTimeline:SlotTimeline = animationData.getSlotTimeline(boneData.name); + if(!timeline && !slotTimeline) + { + continue; + } + + var slotData:SlotData = null; + if(slotDataList) + { + for each(slotData in slotDataList) + { + //找到属于当前Bone的slot(FLash Pro制作的动画一个Bone只包含一个slot) + if(slotData.parent == boneData.name) + { + break; + } + } + } + + var frameList:Vector. = timeline.frameList; + if (slotTimeline) + { + var slotFrameList:Vector. = slotTimeline.frameList; + } + + var originTransform:DBTransform = null; + var originPivot:Point = null; + var prevFrame:TransformFrame = null; + var frameListLength:uint = frameList.length; + for(var j:int = 0;j < frameListLength;j ++) + { + var frame:TransformFrame = frameList[j] as TransformFrame; + //计算frame的transform信息 + setFrameTransform(animationData, armatureData, boneData, frame); + + //转换成相对骨架的transform信息 + frame.transform.x -= boneData.transform.x; + frame.transform.y -= boneData.transform.y; + frame.transform.skewX -= boneData.transform.skewX; + frame.transform.skewY -= boneData.transform.skewY; + frame.transform.scaleX /= boneData.transform.scaleX; + frame.transform.scaleY /= boneData.transform.scaleY; + + //if(!timeline.transformed) + //{ + //if(slotData) + //{ + ////frame.zOrder -= slotData.zOrder; + //} + //} + + //如果originTransform不存在说明当前帧是第一帧,将当前帧的transform保存至timeline的originTransform + //if(!originTransform) + //{ + //originTransform = timeline.originTransform; + //originTransform.copy(frame.transform); + //originTransform.skewX = TransformUtil.formatRadian(originTransform.skewX); + //originTransform.skewY = TransformUtil.formatRadian(originTransform.skewY); + //originPivot = timeline.originPivot; + //originPivot.x = frame.pivot.x; + //originPivot.y = frame.pivot.y; + //} + // + //frame.transform.x -= originTransform.x; + //frame.transform.y -= originTransform.y; + //frame.transform.skewX = TransformUtil.formatRadian(frame.transform.skewX - originTransform.skewX); + //frame.transform.skewY = TransformUtil.formatRadian(frame.transform.skewY - originTransform.skewY); + //frame.transform.scaleX /= originTransform.scaleX; + //frame.transform.scaleY /= originTransform.scaleY; + // + //if(!timeline.transformed) + //{ + //frame.pivot.x -= originPivot.x; + //frame.pivot.y -= originPivot.y; + //} + + if(prevFrame) + { + var dLX:Number = frame.transform.skewX - prevFrame.transform.skewX; + + if(prevFrame.tweenRotate) + { + + if(prevFrame.tweenRotate > 0) + { + if(dLX < 0) + { + frame.transform.skewX += Math.PI * 2; + frame.transform.skewY += Math.PI * 2; + } + + if(prevFrame.tweenRotate > 1) + { + frame.transform.skewX += Math.PI * 2 * (prevFrame.tweenRotate - 1); + frame.transform.skewY += Math.PI * 2 * (prevFrame.tweenRotate - 1); + } + } + else + { + if(dLX > 0) + { + frame.transform.skewX -= Math.PI * 2; + frame.transform.skewY -= Math.PI * 2; + } + + if(prevFrame.tweenRotate < 1) + { + frame.transform.skewX += Math.PI * 2 * (prevFrame.tweenRotate + 1); + frame.transform.skewY += Math.PI * 2 * (prevFrame.tweenRotate + 1); + } + } + } + else + { + frame.transform.skewX = prevFrame.transform.skewX + TransformUtil.formatRadian(frame.transform.skewX - prevFrame.transform.skewX); + frame.transform.skewY = prevFrame.transform.skewY + TransformUtil.formatRadian(frame.transform.skewY - prevFrame.transform.skewY); + } + } + prevFrame = frame; + } + + if (slotTimeline && slotFrameList) + { + frameListLength = slotFrameList.length; + for(j = 0;j < frameListLength;j ++) + { + var slotFrame:SlotFrame = slotFrameList[j] as SlotFrame; + + if(!slotTimeline.transformed) + { + if(slotData) + { + slotFrame.zOrder -= slotData.zOrder; + } + } + } + slotTimeline.transformed = true; + } + + timeline.transformed = true; + + } + } + + //计算frame的transoform信息 + private static function setFrameTransform(animationData:AnimationData, armatureData:ArmatureData, boneData:BoneData, frame:TransformFrame):void + { + frame.transform.copy(frame.global); + //找到当前bone的父亲列表 并将timeline信息存入parentTimelineList 将boneData信息存入parentDataList + var parentData:BoneData = armatureData.getBoneData(boneData.parent); + if(parentData) + { + var parentTimeline:TransformTimeline = animationData.getTimeline(parentData.name); + if(parentTimeline) + { + var parentTimelineList:Vector. = new Vector.; + var parentDataList:Vector. = new Vector.; + while(parentTimeline) + { + parentTimelineList.push(parentTimeline); + parentDataList.push(parentData); + parentData = armatureData.getBoneData(parentData.parent); + if(parentData) + { + parentTimeline = animationData.getTimeline(parentData.name); + } + else + { + parentTimeline = null; + } + } + + var i:int = parentTimelineList.length; + + var globalTransform:DBTransform; + var globalTransformMatrix:Matrix = new Matrix(); + + var currentTransform:DBTransform = new DBTransform(); + var currentTransformMatrix:Matrix = new Matrix(); + //从根开始遍历 + while(i --) + { + parentTimeline = parentTimelineList[i]; + parentData = parentDataList[i]; + //一级一级找到当前帧对应的每个父节点的transform(相对transform) 保存到currentTransform,globalTransform保存根节点的transform + getTimelineTransform(parentTimeline, frame.position, currentTransform, !globalTransform); + + if(!globalTransform) + { + globalTransform = new DBTransform(); + globalTransform.copy(currentTransform); + } + else + { + currentTransform.x += parentTimeline.originTransform.x + parentData.transform.x; + currentTransform.y += parentTimeline.originTransform.y + parentData.transform.y; + + currentTransform.skewX += parentTimeline.originTransform.skewX + parentData.transform.skewX; + currentTransform.skewY += parentTimeline.originTransform.skewY + parentData.transform.skewY; + + currentTransform.scaleX *= parentTimeline.originTransform.scaleX * parentData.transform.scaleX; + currentTransform.scaleY *= parentTimeline.originTransform.scaleY * parentData.transform.scaleY; + + TransformUtil.transformToMatrix(currentTransform, currentTransformMatrix); + currentTransformMatrix.concat(globalTransformMatrix); + TransformUtil.matrixToTransform(currentTransformMatrix, globalTransform, currentTransform.scaleX * globalTransform.scaleX >= 0, currentTransform.scaleY * globalTransform.scaleY >= 0); + + } + + TransformUtil.transformToMatrix(globalTransform, globalTransformMatrix); + } +// TransformUtil.globalToLocal(frame.transform, globalTransform); + frame.transform.divParent(globalTransform); + } + } + } + + private static function getTimelineTransform(timeline:TransformTimeline, position:int, retult:DBTransform, isGlobal:Boolean):void + { + var frameList:Vector. = timeline.frameList; + var i:int = frameList.length; + + while(i --) + { + var currentFrame:TransformFrame = frameList[i] as TransformFrame; + //找到穿越当前帧的关键帧 + if(currentFrame.position <= position && currentFrame.position + currentFrame.duration > position) + { + //是最后一帧或者就是当前帧 + if(i == frameList.length - 1 || position == currentFrame.position) + { + retult.copy(isGlobal?currentFrame.global:currentFrame.transform); + } + else + { + var tweenEasing:Number = currentFrame.tweenEasing; + var progress:Number = (position - currentFrame.position) / currentFrame.duration; + if(tweenEasing && tweenEasing != 10) + { + progress = MathUtil.getEaseValue(progress, tweenEasing); + } + var nextFrame:TransformFrame = frameList[i + 1] as TransformFrame; + + var currentTransform:DBTransform = isGlobal?currentFrame.global:currentFrame.transform; + var nextTransform:DBTransform = isGlobal?nextFrame.global:nextFrame.transform; + + retult.x = currentTransform.x + (nextTransform.x - currentTransform.x) * progress; + retult.y = currentTransform.y + (nextTransform.y - currentTransform.y) * progress; + retult.skewX = TransformUtil.formatRadian(currentTransform.skewX + (nextTransform.skewX - currentTransform.skewX) * progress); + retult.skewY = TransformUtil.formatRadian(currentTransform.skewY + (nextTransform.skewY - currentTransform.skewY) * progress); + retult.scaleX = currentTransform.scaleX + (nextTransform.scaleX - currentTransform.scaleX) * progress; + retult.scaleY = currentTransform.scaleY + (nextTransform.scaleY - currentTransform.scaleY) * progress; + } + break; + } + } + } + + public static function addHideTimeline(animationData:AnimationData, armatureData:ArmatureData):void + { + var boneDataList:Vector. =armatureData.boneDataList; + var i:int = boneDataList.length; + + while(i --) + { + var boneData:BoneData = boneDataList[i]; + var boneName:String = boneData.name; + if(!animationData.getTimeline(boneName)) + { + if(animationData.hideTimelineNameMap.indexOf(boneName) < 0) + { + animationData.hideTimelineNameMap.fixed = false; + animationData.hideTimelineNameMap.push(boneName); + animationData.hideTimelineNameMap.fixed = true; + } + } + } + } + } +} \ No newline at end of file diff --git a/srclib/dragonBones/utils/FactoryUtils.as b/srclib/dragonBones/utils/FactoryUtils.as new file mode 100644 index 00000000..be7c8b64 --- /dev/null +++ b/srclib/dragonBones/utils/FactoryUtils.as @@ -0,0 +1,11 @@ +package dragonBones.utils +{ + public class FactoryUtils + { + public function FactoryUtils() + { + } + } + + +} \ No newline at end of file diff --git a/srclib/dragonBones/utils/MathUtil.as b/srclib/dragonBones/utils/MathUtil.as new file mode 100644 index 00000000..0682ac2a --- /dev/null +++ b/srclib/dragonBones/utils/MathUtil.as @@ -0,0 +1,29 @@ +package dragonBones.utils +{ + /** @private */ + final public class MathUtil + { + /** @private */ + public static function getEaseValue(value:Number, easing:Number):Number + { + var valueEase:Number = 1; + if(easing > 1) //ease in out + { + valueEase = 0.5 * (1 - Math.cos(value * Math.PI)); + easing -= 1; + } + else if (easing > 0) //ease out + { + valueEase = 1 - Math.pow(1-value,2); + } + else if (easing < 0) //ease in + { + easing *= -1; + valueEase = Math.pow(value,2); + } + + return (valueEase - value) * easing + value; + } + } + +} \ No newline at end of file diff --git a/srclib/dragonBones/utils/TransformUtil.as b/srclib/dragonBones/utils/TransformUtil.as new file mode 100644 index 00000000..8645d28d --- /dev/null +++ b/srclib/dragonBones/utils/TransformUtil.as @@ -0,0 +1,102 @@ +package dragonBones.utils { + + import dragonBones.objects.DBTransform; + + import flash.geom.Matrix; + + /** + * @author CG + */ + final public class TransformUtil + { + public static const ANGLE_TO_RADIAN:Number = Math.PI / 180; + public static const RADIAN_TO_ANGLE:Number = 180 / Math.PI; + + private static const HALF_PI:Number = Math.PI * 0.5; + private static const DOUBLE_PI:Number = Math.PI * 2; + + private static const _helpTransformMatrix:Matrix = new Matrix(); + private static const _helpParentTransformMatrix:Matrix = new Matrix(); + + public static function transformToMatrix(transform:DBTransform, matrix:Matrix):void + { + matrix.a = transform.scaleX * Math.cos(transform.skewY) + matrix.b = transform.scaleX * Math.sin(transform.skewY) + matrix.c = -transform.scaleY * Math.sin(transform.skewX); + matrix.d = transform.scaleY * Math.cos(transform.skewX); + matrix.tx = transform.x; + matrix.ty = transform.y; + } + + public static function formatRadian(radian:Number):Number + { + //radian %= DOUBLE_PI; + if (radian > Math.PI) + { + radian -= DOUBLE_PI; + } + if (radian < -Math.PI) + { + radian += DOUBLE_PI; + } + return radian; + } + + //这个算法如果用于骨骼间的绝对转相对请改为DBTransform.divParent()方法 + public static function globalToLocal(transform:DBTransform, parent:DBTransform):void + { + transformToMatrix(transform, _helpTransformMatrix); + transformToMatrix(parent, _helpParentTransformMatrix); + + _helpParentTransformMatrix.invert(); + _helpTransformMatrix.concat(_helpParentTransformMatrix); + + matrixToTransform(_helpTransformMatrix, transform, transform.scaleX * parent.scaleX >= 0, transform.scaleY * parent.scaleY >= 0); + } + + public static function matrixToTransform(matrix:Matrix, transform:DBTransform, scaleXF:Boolean, scaleYF:Boolean):void + { + transform.x = matrix.tx; + transform.y = matrix.ty; + transform.scaleX = Math.sqrt(matrix.a * matrix.a + matrix.b * matrix.b) * (scaleXF ? 1 : -1); + transform.scaleY = Math.sqrt(matrix.d * matrix.d + matrix.c * matrix.c) * (scaleYF ? 1 : -1); + + var skewXArray:Array = []; + skewXArray[0] = Math.acos(matrix.d / transform.scaleY); + skewXArray[1] = -skewXArray[0]; + skewXArray[2] = Math.asin(-matrix.c / transform.scaleY); + skewXArray[3] = skewXArray[2] >= 0 ? Math.PI - skewXArray[2] : skewXArray[2] - Math.PI; + + if(Number(skewXArray[0]).toFixed(4) == Number(skewXArray[2]).toFixed(4) || Number(skewXArray[0]).toFixed(4) == Number(skewXArray[3]).toFixed(4)) + { + transform.skewX = skewXArray[0]; + } + else + { + transform.skewX = skewXArray[1]; + } + + var skewYArray:Array = []; + skewYArray[0] = Math.acos(matrix.a / transform.scaleX); + skewYArray[1] = -skewYArray[0]; + skewYArray[2] = Math.asin(matrix.b / transform.scaleX); + skewYArray[3] = skewYArray[2] >= 0 ? Math.PI - skewYArray[2] : skewYArray[2] - Math.PI; + + if(Number(skewYArray[0]).toFixed(4) == Number(skewYArray[2]).toFixed(4) || Number(skewYArray[0]).toFixed(4) == Number(skewYArray[3]).toFixed(4)) + { + transform.skewY = skewYArray[0]; + } + else + { + transform.skewY = skewYArray[1]; + } + } + //确保角度在-180到180之间 + public static function normalizeRotation(rotation:Number):Number + { + rotation = (rotation + Math.PI)%(2*Math.PI); + rotation = rotation > 0 ? rotation : 2*Math.PI + rotation; + return rotation - Math.PI; + } + } +} \ No newline at end of file diff --git a/srclib/starling/extensions/filters/ThresholdFilter.as b/srclib/starling/extensions/filters/ThresholdFilter.as new file mode 100644 index 00000000..065e46a6 --- /dev/null +++ b/srclib/starling/extensions/filters/ThresholdFilter.as @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2012 Andy Saia + * + * 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. + */ + +package starling.extensions.filters { + + import starling.filters.FragmentFilter; + import starling.textures.Texture; + + import flash.display3D.Context3D; + import flash.display3D.Context3DProgramType; + import flash.display3D.Program3D; + + public class ThresholdFilter extends FragmentFilter + { + private var mShaderProgram:Program3D; + private var mThresholdValue:Number; + private var mThreshold:Vector.; + + public function ThresholdFilter(threshold:Number) + { + mThresholdValue = threshold; + mThreshold = Vector.([ 0, 0, 0, mThresholdValue]); + } + + public override function dispose():void + { + if (mShaderProgram) mShaderProgram.dispose(); + super.dispose(); + } + + protected override function createPrograms():void + { + var vertexShaderString:String = + "m44 op, va0, vc0 \n" + // 4x4 matrix transform to output space + "mov v0, va1 \n"; // pass texture coordinates to fragment program + + var fragmentShaderString:String = + "tex ft1, v0, fs0 <2d, linear, nomip> \n" + // just forward texture color + "sub ft1 ft1 fc1.w\n" + // subtracts threshold value from the texture data's alpha channel + "kil ft1.w \n" + // haltes execution for alpah values less then zero + "add ft1 ft1 fc1.w\n" + // adds back threshold value to texture data's alpha channel + "mov oc, ft1 \n"; //outputs the resulting image + + mShaderProgram = assembleAgal(fragmentShaderString, vertexShaderString); + } + + protected override function activate(pass:int, context:Context3D, texture:Texture):void + { + // already set by super class: + // + // vertex constants 0-3: mvpMatrix (3D) + // vertex attribute 0: vertex position (FLOAT_2) + // vertex attribute 1: texture coordinates (FLOAT_2) + // texture 0: input texture + + + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, mThreshold, 1); //sets this vector to fc0 + + //var valueToSet:Vector. = Vector.([0,0,0, mThresholdValue]); + context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, mThreshold, 1); //sets this vector to fc1 + + context.setProgram(mShaderProgram); + } + + override protected function deactivate(pass:int, context:Context3D, texture:Texture):void + { + } + + //--------------- + // getters and setters + //--------------- + + public function get thresholdValue():Number + { + return mThresholdValue; + } + + /** + * alpha value threshold + * @param value between 0 and 1 + */ + public function set thresholdValue(value:Number):void + { + mThresholdValue = value; + mThreshold[3] = mThresholdValue; + } + } +} \ No newline at end of file diff --git a/srclib/starling/extensions/particles/ColorArgb.as b/srclib/starling/extensions/particles/ColorArgb.as new file mode 100644 index 00000000..b41c64cf --- /dev/null +++ b/srclib/starling/extensions/particles/ColorArgb.as @@ -0,0 +1,84 @@ +// ================================================================================================= +// +// Starling Framework - Particle System Extension +// Copyright 2011 Gamua OG. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.extensions.particles +{ + public class ColorArgb + { + public var red:Number; + public var green:Number; + public var blue:Number; + public var alpha:Number; + + public static function fromRgb(color:uint):ColorArgb + { + var rgb:ColorArgb = new ColorArgb(); + rgb.fromRgb(color); + return rgb; + } + + public static function fromArgb(color:uint):ColorArgb + { + var argb:ColorArgb = new ColorArgb(); + argb.fromArgb(color); + return argb; + } + + public function ColorArgb(red:Number=0, green:Number=0, blue:Number=0, alpha:Number=0) + { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + } + + public function toRgb():uint + { + var r:Number = red; if (r < 0.0) r = 0.0; else if (r > 1.0) r = 1.0; + var g:Number = green; if (g < 0.0) g = 0.0; else if (g > 1.0) g = 1.0; + var b:Number = blue; if (b < 0.0) b = 0.0; else if (b > 1.0) b = 1.0; + + return int(r * 255) << 16 | int(g * 255) << 8 | int(b * 255); + } + + public function toArgb():uint + { + var a:Number = alpha; if (a < 0.0) a = 0.0; else if (a > 1.0) a = 1.0; + var r:Number = red; if (r < 0.0) r = 0.0; else if (r > 1.0) r = 1.0; + var g:Number = green; if (g < 0.0) g = 0.0; else if (g > 1.0) g = 1.0; + var b:Number = blue; if (b < 0.0) b = 0.0; else if (b > 1.0) b = 1.0; + + return int(a * 255) << 24 | int(r * 255) << 16 | int(g * 255) << 8 | int(b * 255); + } + + public function fromRgb(color:uint):void + { + red = (color >> 16 & 0xFF) / 255.0; + green = (color >> 8 & 0xFF) / 255.0; + blue = (color & 0xFF) / 255.0; + } + + public function fromArgb(color:uint):void + { + red = (color >> 16 & 0xFF) / 255.0; + green = (color >> 8 & 0xFF) / 255.0; + blue = (color & 0xFF) / 255.0; + alpha = (color >> 24 & 0xFF) / 255.0; + } + + public function copyFrom(argb:ColorArgb):void + { + red = argb.red; + green = argb.green; + blue = argb.blue; + alpha = argb.alpha; + } + } +} \ No newline at end of file diff --git a/srclib/starling/extensions/particles/PDParticle.as b/srclib/starling/extensions/particles/PDParticle.as new file mode 100644 index 00000000..cbeb78ef --- /dev/null +++ b/srclib/starling/extensions/particles/PDParticle.as @@ -0,0 +1,32 @@ +// ================================================================================================= +// +// Starling Framework - Particle System Extension +// Copyright 2012 Gamua OG. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.extensions.particles +{ + public class PDParticle extends Particle + { + public var colorArgb:ColorArgb; + public var colorArgbDelta:ColorArgb; + public var startX:Number, startY:Number; + public var velocityX:Number, velocityY:Number; + public var radialAcceleration:Number; + public var tangentialAcceleration:Number; + public var emitRadius:Number, emitRadiusDelta:Number; + public var emitRotation:Number, emitRotationDelta:Number; + public var rotationDelta:Number; + public var scaleDelta:Number; + + public function PDParticle() + { + colorArgb = new ColorArgb(); + colorArgbDelta = new ColorArgb(); + } + } +} \ No newline at end of file diff --git a/srclib/starling/extensions/particles/PDParticleSystem.as b/srclib/starling/extensions/particles/PDParticleSystem.as new file mode 100644 index 00000000..bab092c3 --- /dev/null +++ b/srclib/starling/extensions/particles/PDParticleSystem.as @@ -0,0 +1,419 @@ +// ================================================================================================= +// +// Starling Framework - Particle System Extension +// Copyright 2012 Gamua OG. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.extensions.particles +{ + import flash.display3D.Context3DBlendFactor; + + import starling.textures.Texture; + import starling.utils.deg2rad; + + public class PDParticleSystem extends ParticleSystem + { + private const EMITTER_TYPE_GRAVITY:int = 0; + private const EMITTER_TYPE_RADIAL:int = 1; + + // emitter configuration // .pex element name + private var mEmitterType:int; // emitterType + private var mEmitterXVariance:Number; // sourcePositionVariance x + private var mEmitterYVariance:Number; // sourcePositionVariance y + + // particle configuration + private var mMaxNumParticles:int; // maxParticles + private var mLifespan:Number; // particleLifeSpan + private var mLifespanVariance:Number; // particleLifeSpanVariance + private var mStartSize:Number; // startParticleSize + private var mStartSizeVariance:Number; // startParticleSizeVariance + private var mEndSize:Number; // finishParticleSize + private var mEndSizeVariance:Number; // finishParticleSizeVariance + private var mEmitAngle:Number; // angle + private var mEmitAngleVariance:Number; // angleVariance + private var mStartRotation:Number; // rotationStart + private var mStartRotationVariance:Number; // rotationStartVariance + private var mEndRotation:Number; // rotationEnd + private var mEndRotationVariance:Number; // rotationEndVariance + + // gravity configuration + private var mSpeed:Number; // speed + private var mSpeedVariance:Number; // speedVariance + private var mGravityX:Number; // gravity x + private var mGravityY:Number; // gravity y + private var mRadialAcceleration:Number; // radialAcceleration + private var mRadialAccelerationVariance:Number; // radialAccelerationVariance + private var mTangentialAcceleration:Number; // tangentialAcceleration + private var mTangentialAccelerationVariance:Number; // tangentialAccelerationVariance + + // radial configuration + private var mMaxRadius:Number; // maxRadius + private var mMaxRadiusVariance:Number; // maxRadiusVariance + private var mMinRadius:Number; // minRadius + private var mMinRadiusVariance:Number; // minRadiusVariance + private var mRotatePerSecond:Number; // rotatePerSecond + private var mRotatePerSecondVariance:Number; // rotatePerSecondVariance + + // color configuration + private var mStartColor:ColorArgb; // startColor + private var mStartColorVariance:ColorArgb; // startColorVariance + private var mEndColor:ColorArgb; // finishColor + private var mEndColorVariance:ColorArgb; // finishColorVariance + + public function PDParticleSystem(config:XML, texture:Texture) + { + parseConfig(config); + + var emissionRate:Number = mMaxNumParticles / mLifespan; + super(texture, emissionRate, mMaxNumParticles, mMaxNumParticles, + mBlendFactorSource, mBlendFactorDestination); + } + + protected override function createParticle():Particle + { + return new PDParticle(); + } + + protected override function initParticle(aParticle:Particle):void + { + var particle:PDParticle = aParticle as PDParticle; + + // for performance reasons, the random variances are calculated inline instead + // of calling a function + + var lifespan:Number = mLifespan + mLifespanVariance * (Math.random() * 2.0 - 1.0); + + particle.currentTime = 0.0; + particle.totalTime = lifespan > 0.0 ? lifespan : 0.0; + + if (lifespan <= 0.0) return; + + particle.x = mEmitterX + mEmitterXVariance * (Math.random() * 2.0 - 1.0); + particle.y = mEmitterY + mEmitterYVariance * (Math.random() * 2.0 - 1.0); + particle.startX = mEmitterX; + particle.startY = mEmitterY; + + var angle:Number = mEmitAngle + mEmitAngleVariance * (Math.random() * 2.0 - 1.0); + var speed:Number = mSpeed + mSpeedVariance * (Math.random() * 2.0 - 1.0); + particle.velocityX = speed * Math.cos(angle); + particle.velocityY = speed * Math.sin(angle); + + var startRadius:Number = mMaxRadius + mMaxRadiusVariance * (Math.random() * 2.0 - 1.0); + var endRadius:Number = mMinRadius + mMinRadiusVariance * (Math.random() * 2.0 - 1.0); + particle.emitRadius = startRadius; + particle.emitRadiusDelta = (endRadius - startRadius) / lifespan; + particle.emitRotation = mEmitAngle + mEmitAngleVariance * (Math.random() * 2.0 - 1.0); + particle.emitRotationDelta = mRotatePerSecond + mRotatePerSecondVariance * (Math.random() * 2.0 - 1.0); + particle.radialAcceleration = mRadialAcceleration + mRadialAccelerationVariance * (Math.random() * 2.0 - 1.0); + particle.tangentialAcceleration = mTangentialAcceleration + mTangentialAccelerationVariance * (Math.random() * 2.0 - 1.0); + + var startSize:Number = mStartSize + mStartSizeVariance * (Math.random() * 2.0 - 1.0); + var endSize:Number = mEndSize + mEndSizeVariance * (Math.random() * 2.0 - 1.0); + if (startSize < 0.1) startSize = 0.1; + if (endSize < 0.1) endSize = 0.1; + particle.scale = startSize / texture.width; + particle.scaleDelta = ((endSize - startSize) / lifespan) / texture.width; + + // colors + + var startColor:ColorArgb = particle.colorArgb; + var colorDelta:ColorArgb = particle.colorArgbDelta; + + startColor.red = mStartColor.red; + startColor.green = mStartColor.green; + startColor.blue = mStartColor.blue; + startColor.alpha = mStartColor.alpha; + + if (mStartColorVariance.red != 0) startColor.red += mStartColorVariance.red * (Math.random() * 2.0 - 1.0); + if (mStartColorVariance.green != 0) startColor.green += mStartColorVariance.green * (Math.random() * 2.0 - 1.0); + if (mStartColorVariance.blue != 0) startColor.blue += mStartColorVariance.blue * (Math.random() * 2.0 - 1.0); + if (mStartColorVariance.alpha != 0) startColor.alpha += mStartColorVariance.alpha * (Math.random() * 2.0 - 1.0); + + var endColorRed:Number = mEndColor.red; + var endColorGreen:Number = mEndColor.green; + var endColorBlue:Number = mEndColor.blue; + var endColorAlpha:Number = mEndColor.alpha; + + if (mEndColorVariance.red != 0) endColorRed += mEndColorVariance.red * (Math.random() * 2.0 - 1.0); + if (mEndColorVariance.green != 0) endColorGreen += mEndColorVariance.green * (Math.random() * 2.0 - 1.0); + if (mEndColorVariance.blue != 0) endColorBlue += mEndColorVariance.blue * (Math.random() * 2.0 - 1.0); + if (mEndColorVariance.alpha != 0) endColorAlpha += mEndColorVariance.alpha * (Math.random() * 2.0 - 1.0); + + colorDelta.red = (endColorRed - startColor.red) / lifespan; + colorDelta.green = (endColorGreen - startColor.green) / lifespan; + colorDelta.blue = (endColorBlue - startColor.blue) / lifespan; + colorDelta.alpha = (endColorAlpha - startColor.alpha) / lifespan; + + // rotation + + var startRotation:Number = mStartRotation + mStartRotationVariance * (Math.random() * 2.0 - 1.0); + var endRotation:Number = mEndRotation + mEndRotationVariance * (Math.random() * 2.0 - 1.0); + + particle.rotation = startRotation; + particle.rotationDelta = (endRotation - startRotation) / lifespan; + } + + protected override function advanceParticle(aParticle:Particle, passedTime:Number):void + { + var particle:PDParticle = aParticle as PDParticle; + + var restTime:Number = particle.totalTime - particle.currentTime; + passedTime = restTime > passedTime ? passedTime : restTime; + particle.currentTime += passedTime; + + if (mEmitterType == EMITTER_TYPE_RADIAL) + { + particle.emitRotation += particle.emitRotationDelta * passedTime; + particle.emitRadius += particle.emitRadiusDelta * passedTime; + particle.x = mEmitterX - Math.cos(particle.emitRotation) * particle.emitRadius; + particle.y = mEmitterY - Math.sin(particle.emitRotation) * particle.emitRadius; + } + else + { + var distanceX:Number = particle.x - particle.startX; + var distanceY:Number = particle.y - particle.startY; + var distanceScalar:Number = Math.sqrt(distanceX*distanceX + distanceY*distanceY); + if (distanceScalar < 0.01) distanceScalar = 0.01; + + var radialX:Number = distanceX / distanceScalar; + var radialY:Number = distanceY / distanceScalar; + var tangentialX:Number = radialX; + var tangentialY:Number = radialY; + + radialX *= particle.radialAcceleration; + radialY *= particle.radialAcceleration; + + var newY:Number = tangentialX; + tangentialX = -tangentialY * particle.tangentialAcceleration; + tangentialY = newY * particle.tangentialAcceleration; + + particle.velocityX += passedTime * (mGravityX + radialX + tangentialX); + particle.velocityY += passedTime * (mGravityY + radialY + tangentialY); + particle.x += particle.velocityX * passedTime; + particle.y += particle.velocityY * passedTime; + } + + particle.scale += particle.scaleDelta * passedTime; + particle.rotation += particle.rotationDelta * passedTime; + + particle.colorArgb.red += particle.colorArgbDelta.red * passedTime; + particle.colorArgb.green += particle.colorArgbDelta.green * passedTime; + particle.colorArgb.blue += particle.colorArgbDelta.blue * passedTime; + particle.colorArgb.alpha += particle.colorArgbDelta.alpha * passedTime; + + particle.color = particle.colorArgb.toRgb(); + particle.alpha = particle.colorArgb.alpha; + } + + private function updateEmissionRate():void + { + emissionRate = mMaxNumParticles / mLifespan; + } + + private function parseConfig(config:XML):void + { + mEmitterXVariance = parseFloat(config.sourcePositionVariance.attribute("x")); + mEmitterYVariance = parseFloat(config.sourcePositionVariance.attribute("y")); + mGravityX = parseFloat(config.gravity.attribute("x")); + mGravityY = parseFloat(config.gravity.attribute("y")); + mEmitterType = getIntValue(config.emitterType); + mMaxNumParticles = getIntValue(config.maxParticles); + mLifespan = Math.max(0.01, getFloatValue(config.particleLifeSpan)); + mLifespanVariance = getFloatValue(config.particleLifespanVariance); + mStartSize = getFloatValue(config.startParticleSize); + mStartSizeVariance = getFloatValue(config.startParticleSizeVariance); + mEndSize = getFloatValue(config.finishParticleSize); + mEndSizeVariance = getFloatValue(config.FinishParticleSizeVariance); + mEmitAngle = deg2rad(getFloatValue(config.angle)); + mEmitAngleVariance = deg2rad(getFloatValue(config.angleVariance)); + mStartRotation = deg2rad(getFloatValue(config.rotationStart)); + mStartRotationVariance = deg2rad(getFloatValue(config.rotationStartVariance)); + mEndRotation = deg2rad(getFloatValue(config.rotationEnd)); + mEndRotationVariance = deg2rad(getFloatValue(config.rotationEndVariance)); + mSpeed = getFloatValue(config.speed); + mSpeedVariance = getFloatValue(config.speedVariance); + mRadialAcceleration = getFloatValue(config.radialAcceleration); + mRadialAccelerationVariance = getFloatValue(config.radialAccelVariance); + mTangentialAcceleration = getFloatValue(config.tangentialAcceleration); + mTangentialAccelerationVariance = getFloatValue(config.tangentialAccelVariance); + mMaxRadius = getFloatValue(config.maxRadius); + mMaxRadiusVariance = getFloatValue(config.maxRadiusVariance); + mMinRadius = getFloatValue(config.minRadius); + mMinRadiusVariance = getFloatValue(config.minRadiusVariance); + mRotatePerSecond = deg2rad(getFloatValue(config.rotatePerSecond)); + mRotatePerSecondVariance = deg2rad(getFloatValue(config.rotatePerSecondVariance)); + mStartColor = getColor(config.startColor); + mStartColorVariance = getColor(config.startColorVariance); + mEndColor = getColor(config.finishColor); + mEndColorVariance = getColor(config.finishColorVariance); + mBlendFactorSource = getBlendFunc(config.blendFuncSource); + mBlendFactorDestination = getBlendFunc(config.blendFuncDestination); + + // compatibility with future Particle Designer versions + // (might fix some of the uppercase/lowercase typos) + + if (isNaN(mEndSizeVariance)) + mEndSizeVariance = getFloatValue(config.finishParticleSizeVariance); + if (isNaN(mLifespan)) + mLifespan = Math.max(0.01, getFloatValue(config.particleLifespan)); + if (isNaN(mLifespanVariance)) + mLifespanVariance = getFloatValue(config.particleLifeSpanVariance); + if (isNaN(mMinRadiusVariance)) + mMinRadiusVariance = 0.0; + + function getIntValue(element:XMLList):int + { + return parseInt(element.attribute("value")); + } + + function getFloatValue(element:XMLList):Number + { + return parseFloat(element.attribute("value")); + } + + function getColor(element:XMLList):ColorArgb + { + var color:ColorArgb = new ColorArgb(); + color.red = parseFloat(element.attribute("red")); + color.green = parseFloat(element.attribute("green")); + color.blue = parseFloat(element.attribute("blue")); + color.alpha = parseFloat(element.attribute("alpha")); + return color; + } + + function getBlendFunc(element:XMLList):String + { + var value:int = getIntValue(element); + switch (value) + { + case 0: return Context3DBlendFactor.ZERO; + case 1: return Context3DBlendFactor.ONE; + case 0x300: return Context3DBlendFactor.SOURCE_COLOR; + case 0x301: return Context3DBlendFactor.ONE_MINUS_SOURCE_COLOR; + case 0x302: return Context3DBlendFactor.SOURCE_ALPHA; + case 0x303: return Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA; + case 0x304: return Context3DBlendFactor.DESTINATION_ALPHA; + case 0x305: return Context3DBlendFactor.ONE_MINUS_DESTINATION_ALPHA; + case 0x306: return Context3DBlendFactor.DESTINATION_COLOR; + case 0x307: return Context3DBlendFactor.ONE_MINUS_DESTINATION_COLOR; + default: throw new ArgumentError("unsupported blending function: " + value); + } + } + } + + public function get emitterType():int { return mEmitterType; } + public function set emitterType(value:int):void { mEmitterType = value; } + + public function get emitterXVariance():Number { return mEmitterXVariance; } + public function set emitterXVariance(value:Number):void { mEmitterXVariance = value; } + + public function get emitterYVariance():Number { return mEmitterYVariance; } + public function set emitterYVariance(value:Number):void { mEmitterYVariance = value; } + + public function get maxNumParticles():int { return mMaxNumParticles; } + public function set maxNumParticles(value:int):void + { + maxCapacity = value; + mMaxNumParticles = maxCapacity; + updateEmissionRate(); + } + + public function get lifespan():Number { return mLifespan; } + public function set lifespan(value:Number):void + { + mLifespan = Math.max(0.01, value); + updateEmissionRate(); + } + + public function get lifespanVariance():Number { return mLifespanVariance; } + public function set lifespanVariance(value:Number):void { mLifespanVariance = value; } + + public function get startSize():Number { return mStartSize; } + public function set startSize(value:Number):void { mStartSize = value; } + + public function get startSizeVariance():Number { return mStartSizeVariance; } + public function set startSizeVariance(value:Number):void { mStartSizeVariance = value; } + + public function get endSize():Number { return mEndSize; } + public function set endSize(value:Number):void { mEndSize = value; } + + public function get endSizeVariance():Number { return mEndSizeVariance; } + public function set endSizeVariance(value:Number):void { mEndSizeVariance = value; } + + public function get emitAngle():Number { return mEmitAngle; } + public function set emitAngle(value:Number):void { mEmitAngle = value; } + + public function get emitAngleVariance():Number { return mEmitAngleVariance; } + public function set emitAngleVariance(value:Number):void { mEmitAngleVariance = value; } + + public function get startRotation():Number { return mStartRotation; } + public function set startRotation(value:Number):void { mStartRotation = value; } + + public function get startRotationVariance():Number { return mStartRotationVariance; } + public function set startRotationVariance(value:Number):void { mStartRotationVariance = value; } + + public function get endRotation():Number { return mEndRotation; } + public function set endRotation(value:Number):void { mEndRotation = value; } + + public function get endRotationVariance():Number { return mEndRotationVariance; } + public function set endRotationVariance(value:Number):void { mEndRotationVariance = value; } + + public function get speed():Number { return mSpeed; } + public function set speed(value:Number):void { mSpeed = value; } + + public function get speedVariance():Number { return mSpeedVariance; } + public function set speedVariance(value:Number):void { mSpeedVariance = value; } + + public function get gravityX():Number { return mGravityX; } + public function set gravityX(value:Number):void { mGravityX = value; } + + public function get gravityY():Number { return mGravityY; } + public function set gravityY(value:Number):void { mGravityY = value; } + + public function get radialAcceleration():Number { return mRadialAcceleration; } + public function set radialAcceleration(value:Number):void { mRadialAcceleration = value; } + + public function get radialAccelerationVariance():Number { return mRadialAccelerationVariance; } + public function set radialAccelerationVariance(value:Number):void { mRadialAccelerationVariance = value; } + + public function get tangentialAcceleration():Number { return mTangentialAcceleration; } + public function set tangentialAcceleration(value:Number):void { mTangentialAcceleration = value; } + + public function get tangentialAccelerationVariance():Number { return mTangentialAccelerationVariance; } + public function set tangentialAccelerationVariance(value:Number):void { mTangentialAccelerationVariance = value; } + + public function get maxRadius():Number { return mMaxRadius; } + public function set maxRadius(value:Number):void { mMaxRadius = value; } + + public function get maxRadiusVariance():Number { return mMaxRadiusVariance; } + public function set maxRadiusVariance(value:Number):void { mMaxRadiusVariance = value; } + + public function get minRadius():Number { return mMinRadius; } + public function set minRadius(value:Number):void { mMinRadius = value; } + + public function get minRadiusVariance():Number { return mMinRadiusVariance; } + public function set minRadiusVariance(value:Number):void { mMinRadiusVariance = value; } + + public function get rotatePerSecond():Number { return mRotatePerSecond; } + public function set rotatePerSecond(value:Number):void { mRotatePerSecond = value; } + + public function get rotatePerSecondVariance():Number { return mRotatePerSecondVariance; } + public function set rotatePerSecondVariance(value:Number):void { mRotatePerSecondVariance = value; } + + public function get startColor():ColorArgb { return mStartColor; } + public function set startColor(value:ColorArgb):void { mStartColor = value; } + + public function get startColorVariance():ColorArgb { return mStartColorVariance; } + public function set startColorVariance(value:ColorArgb):void { mStartColorVariance = value; } + + public function get endColor():ColorArgb { return mEndColor; } + public function set endColor(value:ColorArgb):void { mEndColor = value; } + + public function get endColorVariance():ColorArgb { return mEndColorVariance; } + public function set endColorVariance(value:ColorArgb):void { mEndColorVariance = value; } + } +} diff --git a/srclib/starling/extensions/particles/Particle.as b/srclib/starling/extensions/particles/Particle.as new file mode 100644 index 00000000..41e73130 --- /dev/null +++ b/srclib/starling/extensions/particles/Particle.as @@ -0,0 +1,31 @@ +// ================================================================================================= +// +// Starling Framework - Particle System Extension +// Copyright 2011 Gamua OG. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.extensions.particles +{ + public class Particle + { + public var x:Number; + public var y:Number; + public var scale:Number; + public var rotation:Number; + public var color:uint; + public var alpha:Number; + public var currentTime:Number; + public var totalTime:Number; + + public function Particle() + { + x = y = rotation = currentTime = 0.0; + totalTime = alpha = scale = 1.0; + color = 0xffffff; + } + } +} \ No newline at end of file diff --git a/srclib/starling/extensions/particles/ParticleSystem.as b/srclib/starling/extensions/particles/ParticleSystem.as new file mode 100644 index 00000000..9c8f1744 --- /dev/null +++ b/srclib/starling/extensions/particles/ParticleSystem.as @@ -0,0 +1,521 @@ +// ================================================================================================= +// +// Starling Framework - Particle System Extension +// Copyright 2012 Gamua OG. All Rights Reserved. +// +// This program is free software. You can redistribute and/or modify it +// in accordance with the terms of the accompanying license agreement. +// +// ================================================================================================= + +package starling.extensions.particles +{ + import com.adobe.utils.AGALMiniAssembler; + + import flash.display3D.Context3D; + import flash.display3D.Context3DBlendFactor; + import flash.display3D.Context3DProgramType; + import flash.display3D.Context3DVertexBufferFormat; + import flash.display3D.IndexBuffer3D; + import flash.display3D.Program3D; + import flash.display3D.VertexBuffer3D; + import flash.geom.Matrix; + import flash.geom.Point; + import flash.geom.Rectangle; + + import starling.animation.IAnimatable; + import starling.core.RenderSupport; + import starling.core.Starling; + import starling.display.DisplayObject; + import starling.errors.MissingContextError; + import starling.events.Event; + import starling.textures.Texture; + import starling.textures.TextureSmoothing; + import starling.utils.MatrixUtil; + import starling.utils.VertexData; + + /** Dispatched when emission of particles is finished. */ + [Event(name="complete", type="starling.events.Event")] + + public class ParticleSystem extends DisplayObject implements IAnimatable + { + public static const MAX_NUM_PARTICLES:int = 16383; + + private var mTexture:Texture; + private var mParticles:Vector.; + private var mFrameTime:Number; + + private var mProgram:Program3D; + private var mVertexData:VertexData; + private var mVertexBuffer:VertexBuffer3D; + private var mIndices:Vector.; + private var mIndexBuffer:IndexBuffer3D; + + private var mNumParticles:int; + private var mMaxCapacity:int; + private var mEmissionRate:Number; // emitted particles per second + private var mEmissionTime:Number; + + /** Helper objects. */ + private static var sHelperMatrix:Matrix = new Matrix(); + private static var sHelperPoint:Point = new Point(); + private static var sRenderAlpha:Vector. = new [1.0, 1.0, 1.0, 1.0]; + + protected var mEmitterX:Number; + protected var mEmitterY:Number; + protected var mBlendFactorSource:String; + protected var mBlendFactorDestination:String; + protected var mSmoothing:String; + + public function ParticleSystem(texture:Texture, emissionRate:Number, + initialCapacity:int=128, maxCapacity:int=16383, + blendFactorSource:String=null, blendFactorDest:String=null) + { + if (texture == null) throw new ArgumentError("texture must not be null"); + + mTexture = texture; + mParticles = new Vector.(0, false); + mVertexData = new VertexData(0); + mIndices = new []; + mEmissionRate = emissionRate; + mEmissionTime = 0.0; + mFrameTime = 0.0; + mEmitterX = mEmitterY = 0; + mMaxCapacity = Math.min(MAX_NUM_PARTICLES, maxCapacity); + mSmoothing = TextureSmoothing.BILINEAR; + mBlendFactorSource = blendFactorSource || Context3DBlendFactor.ONE; + mBlendFactorDestination = blendFactorDest || Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA; + + createProgram(); + updatePremultipliedAlpha(); + raiseCapacity(initialCapacity); + + // handle a lost device context + Starling.current.stage3D.addEventListener(Event.CONTEXT3D_CREATE, + onContextCreated, false, 0, true); + } + + public override function dispose():void + { + Starling.current.stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated); + + if (mVertexBuffer) mVertexBuffer.dispose(); + if (mIndexBuffer) mIndexBuffer.dispose(); + + super.dispose(); + } + + private function onContextCreated(event:Object):void + { + createProgram(); + raiseCapacity(0); + } + + private function updatePremultipliedAlpha():void + { + var pma:Boolean = mTexture.premultipliedAlpha; + + // Particle Designer uses special logic for a certain blend factor combination + if (mBlendFactorSource == Context3DBlendFactor.ONE && + mBlendFactorDestination == Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA) + { + mVertexData.premultipliedAlpha = mTexture.premultipliedAlpha; + if (!pma) mBlendFactorSource = Context3DBlendFactor.SOURCE_ALPHA; + } + else + { + mVertexData.premultipliedAlpha = false; + } + } + + protected function createParticle():Particle + { + return new Particle(); + } + + protected function initParticle(particle:Particle):void + { + particle.x = mEmitterX; + particle.y = mEmitterY; + particle.currentTime = 0; + particle.totalTime = 1; + particle.color = Math.random() * 0xffffff; + } + + protected function advanceParticle(particle:Particle, passedTime:Number):void + { + particle.y += passedTime * 250; + particle.alpha = 1.0 - particle.currentTime / particle.totalTime; + particle.scale = 1.0 - particle.alpha; + particle.currentTime += passedTime; + } + + private function raiseCapacity(byAmount:int):void + { + var oldCapacity:int = capacity; + var newCapacity:int = Math.min(mMaxCapacity, oldCapacity + byAmount); + var context:Context3D = Starling.context; + + if (context == null) throw new MissingContextError(); + + var baseVertexData:VertexData = new VertexData(4); + baseVertexData.setTexCoords(0, 0.0, 0.0); + baseVertexData.setTexCoords(1, 1.0, 0.0); + baseVertexData.setTexCoords(2, 0.0, 1.0); + baseVertexData.setTexCoords(3, 1.0, 1.0); + mTexture.adjustVertexData(baseVertexData, 0, 4); + + mParticles.fixed = false; + mIndices.fixed = false; + + for (var i:int=oldCapacity; i 0) + { + mVertexBuffer = context.createVertexBuffer(newCapacity * 4, VertexData.ELEMENTS_PER_VERTEX); + mVertexBuffer.uploadFromVector(mVertexData.rawData, 0, newCapacity * 4); + + mIndexBuffer = context.createIndexBuffer(newCapacity * 6); + mIndexBuffer.uploadFromVector(mIndices, 0, newCapacity * 6); + } + } + + /** Starts the emitter for a certain time. @default infinite time */ + public function start(duration:Number=Number.MAX_VALUE):void + { + if (mEmissionRate != 0) + mEmissionTime = duration; + } + + /** Stops emitting new particles. Depending on 'clearParticles', the existing particles + * will either keep animating until they die or will be removed right away. */ + public function stop(clearParticles:Boolean=false):void + { + mEmissionTime = 0.0; + if (clearParticles) clear(); + } + + /** Removes all currently active particles. */ + public function clear():void + { + mNumParticles = 0; + } + + /** Returns an empty rectangle at the particle system's position. Calculating the + * actual bounds would be too expensive. */ + public override function getBounds(targetSpace:DisplayObject, + resultRect:Rectangle=null):Rectangle + { + if (resultRect == null) resultRect = new Rectangle(); + + getTransformationMatrix(targetSpace, sHelperMatrix); + MatrixUtil.transformCoords(sHelperMatrix, 0, 0, sHelperPoint); + + resultRect.x = sHelperPoint.x; + resultRect.y = sHelperPoint.y; + resultRect.width = resultRect.height = 0; + + return resultRect; + } + + public function advanceTime(passedTime:Number):void + { + var particleIndex:int = 0; + var particle:Particle; + + // advance existing particles + + while (particleIndex < mNumParticles) + { + particle = mParticles[particleIndex] as Particle; + + if (particle.currentTime < particle.totalTime) + { + advanceParticle(particle, passedTime); + ++particleIndex; + } + else + { + if (particleIndex != mNumParticles - 1) + { + var nextParticle:Particle = mParticles[int(mNumParticles-1)] as Particle; + mParticles[int(mNumParticles-1)] = particle; + mParticles[particleIndex] = nextParticle; + } + + --mNumParticles; + + if (mNumParticles == 0 && mEmissionTime == 0) + dispatchEventWith(Event.COMPLETE); + } + } + + // create and advance new particles + + if (mEmissionTime > 0) + { + var timeBetweenParticles:Number = 1.0 / mEmissionRate; + mFrameTime += passedTime; + + while (mFrameTime > 0) + { + if (mNumParticles < mMaxCapacity) + { + if (mNumParticles == capacity) + raiseCapacity(capacity); + + particle = mParticles[mNumParticles] as Particle; + initParticle(particle); + + // particle might be dead at birth + if (particle.totalTime > 0.0) + { + advanceParticle(particle, mFrameTime); + ++mNumParticles + } + } + + mFrameTime -= timeBetweenParticles; + } + + if (mEmissionTime != Number.MAX_VALUE) + mEmissionTime = Math.max(0.0, mEmissionTime - passedTime); + + if (mNumParticles == 0 && mEmissionTime == 0) + dispatchEventWith(Event.COMPLETE); + } + + // update vertex data + + var vertexID:int = 0; + var color:uint; + var alpha:Number; + var rotation:Number; + var x:Number, y:Number; + var xOffset:Number, yOffset:Number; + var textureWidth:Number = mTexture.width; + var textureHeight:Number = mTexture.height; + + for (var i:int=0; i> 1; + yOffset = textureHeight * particle.scale >> 1; + + for (var j:int=0; j<4; ++j) + mVertexData.setColorAndAlpha(vertexID+j, color, alpha); + + if (rotation) + { + var cos:Number = Math.cos(rotation); + var sin:Number = Math.sin(rotation); + var cosX:Number = cos * xOffset; + var cosY:Number = cos * yOffset; + var sinX:Number = sin * xOffset; + var sinY:Number = sin * yOffset; + + mVertexData.setPosition(vertexID, x - cosX + sinY, y - sinX - cosY); + mVertexData.setPosition(vertexID+1, x + cosX + sinY, y + sinX - cosY); + mVertexData.setPosition(vertexID+2, x - cosX - sinY, y - sinX + cosY); + mVertexData.setPosition(vertexID+3, x + cosX - sinY, y + sinX + cosY); + } + else + { + // optimization for rotation == 0 + mVertexData.setPosition(vertexID, x - xOffset, y - yOffset); + mVertexData.setPosition(vertexID+1, x + xOffset, y - yOffset); + mVertexData.setPosition(vertexID+2, x - xOffset, y + yOffset); + mVertexData.setPosition(vertexID+3, x + xOffset, y + yOffset); + } + } + } + + public override function render(support:RenderSupport, alpha:Number):void + { + if (mNumParticles == 0) return; + + // always call this method when you write custom rendering code! + // it causes all previously batched quads/images to render. + support.finishQuadBatch(); + + // make this call to keep the statistics display in sync. + // to play it safe, it's done in a backwards-compatible way here. + if (support.hasOwnProperty("raiseDrawCount")) + support.raiseDrawCount(); + + alpha *= this.alpha; + + var context:Context3D = Starling.context; + var pma:Boolean = texture.premultipliedAlpha; + + sRenderAlpha[0] = sRenderAlpha[1] = sRenderAlpha[2] = pma ? alpha : 1.0; + sRenderAlpha[3] = alpha; + + if (context == null) throw new MissingContextError(); + + mVertexBuffer.uploadFromVector(mVertexData.rawData, 0, mNumParticles * 4); + mIndexBuffer.uploadFromVector(mIndices, 0, mNumParticles * 6); + + context.setBlendFactors(mBlendFactorSource, mBlendFactorDestination); + context.setTextureAt(0, mTexture.base); + + context.setProgram(mProgram); + context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, support.mvpMatrix3D, true); + context.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, sRenderAlpha, 1); + context.setVertexBufferAt(0, mVertexBuffer, VertexData.POSITION_OFFSET, Context3DVertexBufferFormat.FLOAT_2); + context.setVertexBufferAt(1, mVertexBuffer, VertexData.COLOR_OFFSET, Context3DVertexBufferFormat.FLOAT_4); + context.setVertexBufferAt(2, mVertexBuffer, VertexData.TEXCOORD_OFFSET, Context3DVertexBufferFormat.FLOAT_2); + + context.drawTriangles(mIndexBuffer, 0, mNumParticles * 2); + + context.setTextureAt(0, null); + context.setVertexBufferAt(0, null); + context.setVertexBufferAt(1, null); + context.setVertexBufferAt(2, null); + } + + /** Initialize the ParticleSystem with particles distributed randomly throughout + * their lifespans. */ + public function populate(count:int):void + { + count = Math.min(count, mMaxCapacity - mNumParticles); + + if (mNumParticles + count > capacity) + raiseCapacity(mNumParticles + count - capacity); + + var p:Particle; + for (var i:int=0; i 0 && mEmissionRate > 0; } + public function get capacity():int { return mVertexData.numVertices / 4; } + public function get numParticles():int { return mNumParticles; } + + public function get maxCapacity():int { return mMaxCapacity; } + public function set maxCapacity(value:int):void + { + mMaxCapacity = Math.min(MAX_NUM_PARTICLES, value); + } + + public function get emissionRate():Number { return mEmissionRate; } + public function set emissionRate(value:Number):void { mEmissionRate = value; } + + public function get emitterX():Number { return mEmitterX; } + public function set emitterX(value:Number):void { mEmitterX = value; } + + public function get emitterY():Number { return mEmitterY; } + public function set emitterY(value:Number):void { mEmitterY = value; } + + public function get blendFactorSource():String { return mBlendFactorSource; } + public function set blendFactorSource(value:String):void + { + mBlendFactorSource = value; + updatePremultipliedAlpha(); + } + + public function get blendFactorDestination():String { return mBlendFactorDestination; } + public function set blendFactorDestination(value:String):void + { + mBlendFactorDestination = value; + updatePremultipliedAlpha(); + } + + public function get texture():Texture { return mTexture; } + public function set texture(value:Texture):void + { + if (value == null) throw new ArgumentError("Texture cannot be null"); + + mTexture = value; + createProgram(); + updatePremultipliedAlpha(); + + for (var i:int = mVertexData.numVertices - 4; i >= 0; i -= 4) + { + mVertexData.setTexCoords(i + 0, 0.0, 0.0); + mVertexData.setTexCoords(i + 1, 1.0, 0.0); + mVertexData.setTexCoords(i + 2, 0.0, 1.0); + mVertexData.setTexCoords(i + 3, 1.0, 1.0); + mTexture.adjustVertexData(mVertexData, i, 4); + } + } + + public function get smoothing():String { return mSmoothing; } + public function set smoothing(value:String):void { mSmoothing = value; } + } +} diff --git a/srclib/starling/extensions/textureAtlas/DynamicAtlas.as b/srclib/starling/extensions/textureAtlas/DynamicAtlas.as new file mode 100644 index 00000000..cabddb73 --- /dev/null +++ b/srclib/starling/extensions/textureAtlas/DynamicAtlas.as @@ -0,0 +1,582 @@ +package starling.extensions.textureAtlas { + + import starling.text.BitmapFont; + import starling.text.TextField; + import starling.textures.Texture; + import starling.textures.TextureAtlas; + + import flash.display.BitmapData; + import flash.display.DisplayObject; + import flash.display.MovieClip; + import flash.display.Sprite; + import flash.geom.ColorTransform; + import flash.geom.Matrix; + import flash.geom.Rectangle; + import flash.text.AntiAliasType; + import flash.text.Font; + import flash.text.TextFieldAutoSize; + import flash.text.TextFormat; + import flash.utils.getQualifiedClassName; + + /** + * DynamicAtlas.as + * https://github.com/emibap/Dynamic-Texture-Atlas-Generator + * @author Emibap (Emiliano Angelini) - http://www.emibap.com + * Contribution by Thomas Haselwanter - https://github.com/thomashaselwanter + * Most of this comes thanks to the inspiration (and code) of Thibault Imbert (http://www.bytearray.org) and Nicolas Gans (http://www.flashxpress.net/) + * + * Dynamic Texture Atlas and Bitmap Font Generator (Starling framework Extension) + * ======== + * + * This tool will convert any MovieClip containing Other MovieClips, Sprites or Graphics into a starling Texture Atlas, all in runtime. + * It can also register bitmap Fonts from system or embedded regular fonts. + * By using it, you won't have to statically create your spritesheets or fonts. For instance, you can just take a regular MovieClip containing all the display objects you wish to put into your Altas, and convert everything from vectors to bitmap textures. + * Or you can select which font (specifying characters) you'd like to register as a Bitmap Font, using a string or passing a Regular TextField as a parameter. + * This extension could save you a lot of time specially if you'll be coding mobile apps with the [starling framework](http://www.starling-framework.org/). + * + * # version 1.0 # + * - Added the checkBounds parameter to scan the clip prior the rasterization in order to get the bounds of the entire MovieClip (prevent scaling in some cases). Thank you Aymeric Lamboley. + * - Added the fontCustomID parameter to the Bitmap font creation. Thank you Regan. + * + * ### Features ### + * + * * Dynamic creation of a Texture Atlas from a MovieClip (flash.display.MovieClip) container that could act as a sprite sheet, or from a Vector of Classes + * * Filters made to the objects are captured + * * Color transforms (tint, alpha) are optionally captured + * * Scales the objects (and also the filters) to a specified value + * * Automatically detects the objects bounds so you don't necessarily have to set the registration points to TOP LEFT + * * Registers Bitmap Fonts based on system or embedded fonts from strings or from good old Flash TextFields + * + * ### TODO List ### + * + * * Further code optimization + * * A better implementation of the Bitmap Font creation process + * * Documentation (?) + * + * ### Whish List ### + * * Optional division of the process into small intervals (for smooth performance of the app) + * + * ### Usage ### + * + * You can use the following static methods (examples at the gitHub Repo): + * + * [Texture Atlas creation] + * - DynamicAtlas.fromMovieClipContainer(swf:flash.display.MovieClip, scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true):starling.textures.TextureAtlas + * - DynamicAtlas.fromClassVector(assets:Vector., scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true):starling.textures.TextureAtlas + * + * [Bitmap Font registration] + * - DynamicAtlas.bitmapFontFromString(chars:String, fontFamily:String, fontSize:Number = 12, bold:Boolean = false, italic:Boolean = false, charMarginX:int=0):void + * - DynamicAtlas.bitmapFontFromTextField(tf:flash.text.TextField, charMarginX:int=0):void + * + * Enclose inside a try/catch for error handling: + * try { + * var atlas:TextureAtlas = DynamicAtlas.fromMovieClipContainer(mc); + * } catch (e:Error) { + * trace("There was an error in the creation of the texture Atlas. Please check if the dimensions of your clip exceeded the maximun allowed texture size. -", e.message); + * } + * + * History: + * ------- + * # version 0.9.5 # + * - Added the fromClassVector static function. Thank you Thomas Haselwanter + * + * # version 0.9 # + * - Added Bitmap Font creation support + * - Scaling also applies to filters. + * - Added Margin and PreserveColor Properties + * + * # version 0.8 # + * - Added the scaleFactor constructor parameter. Now you can define a custom scale to the final result. + * - Scaling also applies to filters. + * - Added Margin and PreserveColor Properties + * + * # version 0.7 # + * First Public version + **/ + + public class DynamicAtlas + { + static protected const DEFAULT_CANVAS_WIDTH:Number = 640; + + static protected var _items:Array; + static protected var _canvas:Sprite; + + static protected var _currentLab:String; + + static protected var _x:Number; + static protected var _y:Number; + + static protected var _bData:BitmapData; + static protected var _mat:Matrix; + static protected var _margin:Number; + static protected var _preserveColor:Boolean; + + // Will not be used - Only using one static method + public function DynamicAtlas() + { + + } + + // Private methods + + static protected function appendIntToString(num:int, numOfPlaces:int):String + { + var numString:String = num.toString(); + var outString:String = ""; + for (var i:int = 0; i < numOfPlaces - numString.length; i++) + { + outString += "0"; + } + return outString + numString; + } + + static protected function layoutChildren():void + { + var xPos:Number = 0; + var yPos:Number = 0; + var maxY:Number = 0; + var len:int = _items.length; + + var itm:TextureItem; + + for (var i:uint = 0; i < len; i++) + { + itm = _items[i]; + if ((xPos + itm.width) > DEFAULT_CANVAS_WIDTH) + { + xPos = 0; + yPos += maxY; + maxY = 0; + } + if (itm.height + 1 > maxY) + { + maxY = itm.height + 1; + } + itm.x = xPos; + itm.y = yPos; + xPos += itm.width + 1; + } + } + + /** + * isEmbedded + * + * @param fontFamily:Boolean - The name of the Font + * @return Boolean - True if the font is an embedded one + */ + static protected function isEmbedded(fontFamily:String):Boolean + { + var embeddedFonts:Vector. = Vector.(Font.enumerateFonts()); + + for (var i:int = embeddedFonts.length - 1; i > -1 && embeddedFonts[i].fontName != fontFamily; i--) { } + + return (i > -1); + + } + + static protected function getRealBounds(clip:DisplayObject):Rectangle { + var bounds:Rectangle = clip.getBounds(clip.parent); + bounds.x = Math.floor(bounds.x); + bounds.y = Math.floor(bounds.y); + bounds.height = Math.ceil(bounds.height); + bounds.width = Math.ceil(bounds.width); + + var realBounds:Rectangle = new Rectangle(0, 0, bounds.width + _margin * 2, bounds.height + _margin * 2); + + // Checking filters in case we need to expand the outer bounds + if (clip.filters.length > 0) + { + // filters + var j:int = 0; + //var clipFilters:Array = clipChild.filters.concat(); + var clipFilters:Array = clip.filters; + var clipFiltersLength:int = clipFilters.length; + var tmpBData:BitmapData; + var filterRect:Rectangle; + + tmpBData = new BitmapData(realBounds.width, realBounds.height, false); + filterRect = tmpBData.generateFilterRect(tmpBData.rect, clipFilters[j]); + realBounds = realBounds.union(filterRect); + tmpBData.dispose(); + + while (++j < clipFiltersLength) + { + tmpBData = new BitmapData(filterRect.width, filterRect.height, true, 0); + filterRect = tmpBData.generateFilterRect(tmpBData.rect, clipFilters[j]); + realBounds = realBounds.union(filterRect); + tmpBData.dispose(); + } + } + + realBounds.offset(bounds.x, bounds.y); + realBounds.width = Math.max(realBounds.width, 1); + realBounds.height = Math.max(realBounds.height, 1); + + tmpBData = null; + return realBounds; + } + + /** + * drawItem - This will actually rasterize the display object passed as a parameter + * @param clip + * @param name + * @param baseName + * @param clipColorTransform + * @param frameBounds + * @return TextureItem + */ + static protected function drawItem(clip:DisplayObject, name:String = "", baseName:String = "", clipColorTransform:ColorTransform = null, frameBounds:Rectangle=null):TextureItem + { + var realBounds:Rectangle = getRealBounds(clip); + + _bData = new BitmapData(realBounds.width, realBounds.height, true, 0); + _mat = clip.transform.matrix; + _mat.translate(-realBounds.x + _margin, -realBounds.y + _margin); + + _bData.draw(clip, _mat, _preserveColor ? clipColorTransform : null); + + var label:String = ""; + if (clip is MovieClip) { + if (clip["currentLabel"] != _currentLab && clip["currentLabel"] != null) + { + _currentLab = clip["currentLabel"]; + label = _currentLab; + } + } + + if (frameBounds) { + realBounds.x = frameBounds.x - realBounds.x; + realBounds.y = frameBounds.y - realBounds.y; + realBounds.width = frameBounds.width; + realBounds.height = frameBounds.height; + } + + var item:TextureItem = new TextureItem(_bData, name, label, realBounds.x, realBounds.y, realBounds.width, realBounds.height); + + _items.push(item); + _canvas.addChild(item); + + + _bData = null; + + return item; + } + + // Public methods + + /** + * This method takes a vector of DisplayObject class and converts it into a Texture Atlas. + * + * @param assets:Vector. - The DisplayObject classes you wish to convert into a TextureAtlas. Must contain classes whose instances are of type DisplayObject that will be rasterized and become the subtextures of your Atlas. + * @param scaleFactor:Number - The scaling factor to apply to every object. Default value is 1 (no scaling). + * @param margin:uint - The amount of pixels that should be used as the resulting image margin (for each side of the image). Default value is 0 (no margin). + * @param preserveColor:Boolean - A Flag which indicates if the color transforms should be captured or not. Default value is true (capture color transform). + * @param checkBounds:Boolean - A Flag used to scan the clip prior the rasterization in order to get the bounds of the entire MovieClip. By default is false because it adds overhead to the process. + * @return TextureAtlas - The dynamically generated Texture Atlas. + */ + static public function fromClassVector(assets:Vector., scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true, checkBounds:Boolean=false):TextureAtlas + { + var container:MovieClip = new MovieClip(); + for each (var assetClass:Class in assets) { + var assetInstance:DisplayObject = new assetClass(); + assetInstance.name = getQualifiedClassName(assetClass); + container.addChild(assetInstance); + } + return fromMovieClipContainer(container, scaleFactor, margin, preserveColor, checkBounds); + } + + /** Retrieves all textures for a class. Returns null if it is not found. + * This method can be used if TextureAtlass doesn't support classes. + */ + static public function getTexturesByClass(textureAtlas:TextureAtlas, assetClass:Class):Vector. { + return textureAtlas.getTextures(getQualifiedClassName(assetClass)); + } + + /** + * This method will take a MovieClip sprite sheet (containing other display objects) and convert it into a Texture Atlas. + * + * @param swf:MovieClip - The MovieClip sprite sheet you wish to convert into a TextureAtlas. I must contain named instances of every display object that will be rasterized and become the subtextures of your Atlas. + * @param scaleFactor:Number - The scaling factor to apply to every object. Default value is 1 (no scaling). + * @param margin:uint - The amount of pixels that should be used as the resulting image margin (for each side of the image). Default value is 0 (no margin). + * @param preserveColor:Boolean - A Flag which indicates if the color transforms should be captured or not. Default value is true (capture color transform). + * @param checkBounds:Boolean - A Flag used to scan the clip prior the rasterization in order to get the bounds of the entire MovieClip. By default is false because it adds overhead to the process. + * @return TextureAtlas - The dynamically generated Texture Atlas. + */ + static public function fromMovieClipContainer(swf:MovieClip, scaleFactor:Number = 1, margin:uint=0, preserveColor:Boolean = true, checkBounds:Boolean=false):TextureAtlas + { + var parseFrame:Boolean = false; + var selected:DisplayObject; + var selectedTotalFrames:int; + var selectedColorTransform:ColorTransform; + var frameBounds:Rectangle = new Rectangle(0, 0, 0, 0); + + var children:uint = swf.numChildren; + + var canvasData:BitmapData; + + var texture:Texture; + var xml:XML; + var subText:XML; + var atlas:TextureAtlas; + + var itemsLen:int; + var itm:TextureItem; + + var m:uint; + + _margin = margin; + _preserveColor = preserveColor; + + _items = []; + + if (!_canvas) + _canvas = new Sprite(); + + if(swf is MovieClip) + MovieClip(swf).gotoAndStop(1); + + for (var i:uint = 0; i < children; i++) + { + selected = swf.getChildAt(i); + selectedColorTransform = selected.transform.colorTransform; + _x = selected.x; + _y = selected.y; + + // Scaling if needed (including filters) + if (scaleFactor != 1) + { + + selected.scaleX *= scaleFactor; + selected.scaleY *= scaleFactor; + + if (selected.filters.length > 0) + { + var filters:Array = selected.filters; + var filtersLen:int = selected.filters.length; + var filter:Object; + for (var j:uint = 0; j < filtersLen; j++) + { + filter = filters[j]; + + if (filter.hasOwnProperty("blurX")) + { + filter.blurX *= scaleFactor; + filter.blurY *= scaleFactor; + } + if (filter.hasOwnProperty("distance")) + { + filter.distance *= scaleFactor; + } + } + selected.filters = filters; + } + } + + // Not all children will be MCs. Some could be sprites + if (selected is MovieClip) + { + selectedTotalFrames = MovieClip(selected).totalFrames; + // Gets the frame bounds by performing a frame-by-frame check + if (checkBounds) { + MovieClip(selected).gotoAndStop(0); + frameBounds = getRealBounds(selected); + m = 1; + while (++m <= selectedTotalFrames) + { + MovieClip(selected).gotoAndStop(m); + frameBounds = frameBounds.union(getRealBounds(selected)); + } + } + } + else selectedTotalFrames = 1; + m = 0; + // Draw every frame (if MC - else will just be one) + while (++m <= selectedTotalFrames) + { + if (selected is MovieClip) + MovieClip(selected).gotoAndStop(m); + drawItem(selected, selected.name + "_" + appendIntToString(m - 1, 5), selected.name, selectedColorTransform, frameBounds); + } + } + + _currentLab = ""; + + layoutChildren(); + + canvasData = new BitmapData(_canvas.width, _canvas.height, true, 0x000000); + canvasData.draw(_canvas); + + xml = new XML(); + xml.@imagePath = "atlas.png"; + + itemsLen = _items.length; + + for (var k:uint = 0; k < itemsLen; k++) + { + itm = _items[k]; + + itm.graphic.dispose(); + + // xml + subText = new XML(); + subText.@name = itm.textureName; + subText.@x = itm.x; + subText.@y = itm.y; + subText.@width = itm.width; + subText.@height = itm.height; + subText.@frameX = itm.frameX; + subText.@frameY = itm.frameY; + subText.@frameWidth = itm.frameWidth; + subText.@frameHeight = itm.frameHeight; + + if (itm.frameName != "") + subText.@frameLabel = itm.frameName; + xml.appendChild(subText); + } + + texture = Texture.fromBitmapData(canvasData); + atlas = new TextureAtlas(texture, xml); + + _items.length = 0; + _canvas.removeChildren(); + + _items = null; + xml = null; + _canvas = null; + _currentLab = null; + //_x = _y = _margin = null; + + return atlas; + } + + /** + * This method will register a Bitmap Font based on each char that belongs to a String. + * + * @param chars:String - The collection of chars which will become the Bitmap Font + * @param fontFamily:String - The name of the Font that will be converted to a Bitmap Font + * @param fontSize:Number - The size in pixels of the font. + * @param bold:Boolean - A flag indicating if the font will be rasterized as bold. + * @param italic:Boolean - A flag indicating if the font will be rasterized as italic. + * @param charMarginX:int - The number of pixels that each character should have as horizontal margin (negative values are allowed). Default value is 0. + * @param fontCustomID:String - A custom font family name indicated by the user. Helpful when using differnt effects for the same font. [Optional] + */ + static public function bitmapFontFromString(chars:String, fontFamily:String, fontSize:Number = 12, bold:Boolean = false, italic:Boolean = false, charMarginX:int=0, fontCustomID:String=""):void { + var format:TextFormat = new TextFormat(fontFamily, fontSize, 0xFFFFFF, bold, italic); + var tf:flash.text.TextField = new flash.text.TextField(); + + tf.autoSize = TextFieldAutoSize.LEFT; + + + // If the font is an embedded one (I couldn't get to work the Array.indexOf method) :( + if (isEmbedded(fontFamily)) { + tf.antiAliasType = AntiAliasType.ADVANCED; + tf.embedFonts = true; + } + + tf.defaultTextFormat = format; + tf.text = chars; + + if (fontCustomID == "") fontCustomID = fontFamily; + bitmapFontFromTextField(tf, charMarginX, fontCustomID); + } + + /** + * This method will register a Bitmap Font based on each char that belongs to a regular flash TextField, rasterizing filters and color transforms as well. + * + * @param tf:flash.text.TextField - The textfield that will be used to rasterize every char of the text property + * @param charMarginX:int - The number of pixels that each character should have as horizontal margin (negative values are allowed). Default value is 0. + * @param fontCustomID:String - A custom font family name indicated by the user. Helpful when using differnt effects for the same font. [Optional] + */ + static public function bitmapFontFromTextField(tf:flash.text.TextField, charMarginX:int=0, fontCustomID:String=""):void { + var charCol:Vector. = Vector.(tf.text.split("")); + var format:TextFormat = tf.defaultTextFormat; + var fontFamily:String = format.font; + var fontSize:Object = format.size; + + var oldAutoSize:String = tf.autoSize; + tf.autoSize = TextFieldAutoSize.LEFT; + + var canvasData:BitmapData; + var texture:Texture; + var xml:XML; + + var myChar:String; + + _margin = 0; + _preserveColor = true; + + _items = []; + var itm:TextureItem; + var itemsLen:int; + + if (!_canvas) _canvas = new Sprite(); + + // Add the blank space char if not present; + if (charCol.indexOf(" ") == -1) charCol.push(" "); + + for (var i:int = charCol.length - 1; i > -1; i--) { + myChar = tf.text = charCol[i]; + drawItem(tf, myChar.charCodeAt().toString()); + } + + _currentLab = ""; + + layoutChildren(); + + canvasData = new BitmapData(_canvas.width, _canvas.height, true, 0x000000); + canvasData.draw(_canvas); + + itemsLen = _items.length; + + + xml = new XML(); + var infoNode:XML = new XML(); + infoNode.@face = (fontCustomID == "")? fontFamily : fontCustomID; + infoNode.@size = fontSize; + xml.appendChild(infoNode); + //var commonNode:XML = new XML(); + var commonNode:XML = new XML(); + commonNode.@lineHeight = fontSize; + xml.appendChild(commonNode); + xml.appendChild(new XML()); + var charsNode:XML = new XML( ); + charsNode.@count = itemsLen; + var charNode:XML; + + for (var k:uint = 0; k < itemsLen; k++) + { + itm = _items[k]; + + itm.graphic.dispose(); + + // xml + charNode = new XML(); + charNode.@id = itm.textureName; + charNode.@x = itm.x; + charNode.@y = itm.y; + charNode.@width = itm.width; + charNode.@height = itm.height; + charNode.@xadvance = itm.width + 2*charMarginX; + charsNode.appendChild(charNode); + } + + xml.appendChild(charsNode); + + texture = Texture.fromBitmapData(canvasData); + TextField.registerBitmapFont(new BitmapFont(texture, xml)); + + _items.length = 0; + _canvas.removeChildren(); + + tf.autoSize = oldAutoSize; + tf.text = charCol.join(); + + _items = null; + xml = null; + _canvas = null; + _currentLab = null; + } + + } + +} diff --git a/srclib/starling/extensions/textureAtlas/TextureItem.as b/srclib/starling/extensions/textureAtlas/TextureItem.as new file mode 100644 index 00000000..52c9574c --- /dev/null +++ b/srclib/starling/extensions/textureAtlas/TextureItem.as @@ -0,0 +1,66 @@ +package starling.extensions.textureAtlas { + + import flash.display.Bitmap; + import flash.display.BitmapData; + import flash.display.Sprite; + + public class TextureItem extends Sprite{ + + private var _graphic:BitmapData; + private var _textureName:String = ""; + private var _frameName:String = ""; + private var _frameX:int = 0; + private var _frameY:int = 0; + private var _frameWidth:int = 0; + private var _frameHeight:int = 0; + + + public function TextureItem(graphic:BitmapData, textureName:String, frameName:String, frameX:int = 0, frameY:int=0, frameWidth:int=0, frameHeight:int=0){ + super(); + + _graphic = graphic; + _textureName = textureName; + _frameName = frameName; + + _frameWidth = frameWidth; + _frameHeight = frameHeight; + _frameX = frameX; + _frameY = frameY; + + var bm:Bitmap = new Bitmap(graphic, "auto", false); + addChild(bm); + } + + public function get textureName():String{ + return _textureName; + } + + public function get frameName():String{ + return _frameName; + } + + public function get graphic():BitmapData{ + return _graphic; + } + + public function get frameX():int + { + return _frameX; + } + + public function get frameY():int + { + return _frameY; + } + + public function get frameWidth():int + { + return _frameWidth; + } + + public function get frameHeight():int + { + return _frameHeight; + } + } +} \ No newline at end of file diff --git a/srclib/starling/extensions/utils/Line.as b/srclib/starling/extensions/utils/Line.as new file mode 100644 index 00000000..e0d5d0fd --- /dev/null +++ b/srclib/starling/extensions/utils/Line.as @@ -0,0 +1,51 @@ +package starling.extensions.utils { + + import starling.display.Quad; + import starling.display.Sprite; + + /** + * @author Leandro Barreto 2012 + * @version 1.0 + */ + public class Line extends Sprite { + + private var baseQuad:Quad; + private var _thickness:Number = 1; + private var _color:uint = 0x000000; + + public function Line() + { + baseQuad = new Quad(1, _thickness, _color); + addChild(baseQuad); + } + + public function lineTo(toX:int, toY:int):void + { + baseQuad.width = Math.round(Math.sqrt((toX*toX)+(toY*toY))); + baseQuad.rotation = Math.atan2(toY, toX); + } + + public function set thickness(t:Number):void + { + var currentRotation:Number = baseQuad.rotation; + baseQuad.rotation = 0; + baseQuad.height = _thickness = t; + baseQuad.rotation = currentRotation; + } + + public function get thickness():Number + { + return _thickness; + } + + public function set color(c:uint):void + { + baseQuad.color = _color = c; + } + + public function get color():uint + { + return _color; + } + } +}

    CitrusEngine can access to the Stage3D power thanks to the Starling Framework.