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 00000000..a6eac1cb Binary files /dev/null and b/fla/Components.fla differ 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 00000000..83bbe8f5 Binary files /dev/null and b/lib/EazeTween.swc differ diff --git a/lib/feathers.swc b/lib/feathers.swc new file mode 100644 index 00000000..ff5d82f0 Binary files /dev/null and b/lib/feathers.swc differ diff --git a/lib/nape-release.swc b/lib/nape-release.swc new file mode 100644 index 00000000..2f892f9d Binary files /dev/null and b/lib/nape-release.swc differ diff --git a/lib/signals.swc b/lib/signals.swc new file mode 100644 index 00000000..c4f338e6 Binary files /dev/null and b/lib/signals.swc differ diff --git a/lib/starling.swc b/lib/starling.swc new file mode 100644 index 00000000..837278aa Binary files /dev/null and b/lib/starling.swc differ diff --git a/src/citrus/core/CitrusEngine.as b/src/citrus/core/CitrusEngine.as new file mode 100644 index 00000000..fbf01d16 --- /dev/null +++ b/src/citrus/core/CitrusEngine.as @@ -0,0 +1,485 @@ +package citrus.core { + + import citrus.input.Input; + import citrus.sounds.SoundManager; + import citrus.utils.AGameData; + import citrus.utils.LevelManager; + + import org.osflash.signals.Signal; + + import flash.display.MovieClip; + import flash.display.StageAlign; + import flash.display.StageDisplayState; + import flash.display.StageScaleMode; + import flash.events.Event; + import flash.events.FullScreenEvent; + import flash.geom.Matrix; + import flash.media.SoundMixer; + + /** + * CitrusEngine is the top-most class in the library. When you start your project, you should make your + * document class extend this class unless you use Starling. In this case extends StarlingCitrusEngine. + * + *

CitrusEngine 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. + * + *

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

+ */ + 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; + } + } +}