[libgdx] Added a Physics enum to updateWorldTransform and Updateable#update to control how physics and other non-deterministic updates are applied.

This commit is contained in:
Nathan Sweet 2023-09-28 16:35:18 -04:00
parent 9678808aaf
commit 4116af02fa
25 changed files with 264 additions and 60 deletions

View File

@ -34,6 +34,7 @@ import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.attachments.AttachmentLoader; import com.esotericsoftware.spine.attachments.AttachmentLoader;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.attachments.ClippingAttachment; import com.esotericsoftware.spine.attachments.ClippingAttachment;
@ -83,7 +84,7 @@ public class BonePlotting {
float time = 0; float time = 0;
while (time < animation.getDuration()) { while (time < animation.getDuration()) {
animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in); animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
System.out.println(animation.getName() + "," // System.out.println(animation.getName() + "," //
+ bone.getWorldX() + "," + bone.getWorldY() + "," + bone.getWorldRotationX()); + bone.getWorldX() + "," + bone.getWorldY() + "," + bone.getWorldRotationX());

View File

@ -54,6 +54,7 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader; import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
import com.esotericsoftware.spine.attachments.RegionAttachment; import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.Sequence; import com.esotericsoftware.spine.attachments.Sequence;
@ -104,7 +105,7 @@ public class Box2DExample extends ApplicationAdapter {
skeleton = new Skeleton(skeletonData); skeleton = new Skeleton(skeletonData);
skeleton.x = -32; skeleton.x = -32;
skeleton.y = 1; skeleton.y = 1;
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
// See Box2DTest in libgdx for more detailed information about Box2D setup. // See Box2DTest in libgdx for more detailed information about Box2D setup.
camera = new OrthographicCamera(48, 32); camera = new OrthographicCamera(48, 32);
@ -150,7 +151,7 @@ public class Box2DExample extends ApplicationAdapter {
animation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in); animation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in);
skeleton.x += 8 * delta; skeleton.x += 8 * delta;
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
skeletonRenderer.draw(batch, skeleton); skeletonRenderer.draw(batch, skeleton);
batch.end(); batch.end();

View File

@ -42,6 +42,7 @@ import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer; import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** Demonstrates rendering an animation to a frame buffer (FBO) and then rendering the FBO to the screen. */ /** Demonstrates rendering an animation to a frame buffer (FBO) and then rendering the FBO to the screen. */
@ -75,7 +76,7 @@ public class FboTest extends ApplicationAdapter {
// Create a skeleton instance, set the position of its root bone, and update its world transform. // Create a skeleton instance, set the position of its root bone, and update its world transform.
skeleton = new Skeleton(skeletonData); skeleton = new Skeleton(skeletonData);
skeleton.setPosition(250, 20); skeleton.setPosition(250, 20);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
// Create an FBO and a texture region with Y flipped. // Create an FBO and a texture region with Y flipped.
fbo = new FrameBuffer(Pixmap.Format.RGBA8888, 512, 512, false); fbo = new FrameBuffer(Pixmap.Format.RGBA8888, 512, 512, false);

View File

@ -38,6 +38,7 @@ import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3; import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** Demonstrates how to let the target bone of an IK constraint follow the mouse or touch position, which in turn repositions part /** Demonstrates how to let the target bone of an IK constraint follow the mouse or touch position, which in turn repositions part
@ -97,7 +98,7 @@ public class IKTest extends ApplicationAdapter {
// later. // later.
state.update(Gdx.graphics.getDeltaTime()); state.update(Gdx.graphics.getDeltaTime());
state.apply(skeleton); state.apply(skeleton);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
// Position the "crosshair" bone at the mouse // Position the "crosshair" bone at the mouse
// location. We do this before calling // location. We do this before calling
@ -122,7 +123,7 @@ public class IKTest extends ApplicationAdapter {
// Calculate final world transform with the // Calculate final world transform with the
// crosshair bone set to the mouse cursor // crosshair bone set to the mouse cursor
// position. // position.
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
// Clear the screen, update the camera and // Clear the screen, update the camera and
// render the skeleton. // render the skeleton.

View File

@ -37,6 +37,8 @@ import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Demonstrates creating and configuring a new skin at runtime. */ /** Demonstrates creating and configuring a new skin at runtime. */
public class MixAndMatchTest extends ApplicationAdapter { public class MixAndMatchTest extends ApplicationAdapter {
OrthographicCamera camera; OrthographicCamera camera;
@ -92,7 +94,7 @@ public class MixAndMatchTest extends ApplicationAdapter {
ScreenUtils.clear(0, 0, 0, 0); ScreenUtils.clear(0, 0, 0, 0);
state.apply(skeleton); // Poses skeleton using current animations. This sets the bones' local SRT. state.apply(skeleton); // Poses skeleton using current animations. This sets the bones' local SRT.
skeleton.updateWorldTransform(); // Uses the bones' local SRT to compute their world SRT. skeleton.updateWorldTransform(Physics.update); // Uses the bones' local SRT to compute their world SRT.
// Configure the camera, and PolygonSpriteBatch // Configure the camera, and PolygonSpriteBatch
camera.update(); camera.update();

View File

@ -58,6 +58,7 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Demonstrates simplistic usage of lighting with normal maps. /** Demonstrates simplistic usage of lighting with normal maps.
* <p> * <p>
@ -110,7 +111,7 @@ public class NormalMapTest extends ApplicationAdapter {
skeleton = new Skeleton(skeleton); skeleton = new Skeleton(skeleton);
skeleton.setX(ui.prefs.getFloat("x", Gdx.graphics.getWidth() / 2)); skeleton.setX(ui.prefs.getFloat("x", Gdx.graphics.getWidth() / 2));
skeleton.setY(ui.prefs.getFloat("y", Gdx.graphics.getHeight() / 4)); skeleton.setY(ui.prefs.getFloat("y", Gdx.graphics.getHeight() / 4));
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
Gdx.input.setInputProcessor(new InputMultiplexer(ui.stage, new InputAdapter() { Gdx.input.setInputProcessor(new InputMultiplexer(ui.stage, new InputAdapter() {
public boolean touchDown (int screenX, int screenY, int pointer, int button) { public boolean touchDown (int screenX, int screenY, int pointer, int button) {
@ -136,7 +137,7 @@ public class NormalMapTest extends ApplicationAdapter {
float lastTime = time; float lastTime = time;
time += Gdx.graphics.getDeltaTime(); time += Gdx.graphics.getDeltaTime();
if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, MixBlend.first, MixDirection.in); if (animation != null) animation.apply(skeleton, lastTime, time, true, null, 1, MixBlend.first, MixDirection.in);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
lightPosition.x = Gdx.input.getX(); lightPosition.x = Gdx.input.getX();
lightPosition.y = (Gdx.graphics.getHeight() - 1 - Gdx.input.getY()); lightPosition.y = (Gdx.graphics.getHeight() - 1 - Gdx.input.getY());

View File

@ -48,6 +48,7 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** Demonstrates rendering an animation to a frame buffer (FBO) and then writing each frame as a PNG. */ /** Demonstrates rendering an animation to a frame buffer (FBO) and then writing each frame as a PNG. */
@ -81,7 +82,7 @@ public class PngExportTest extends ApplicationAdapter {
// Create a skeleton instance, set the position of its root bone, and update its world transform. // Create a skeleton instance, set the position of its root bone, and update its world transform.
skeleton = new Skeleton(skeletonData); skeleton = new Skeleton(skeletonData);
skeleton.setPosition(250, 20); skeleton.setPosition(250, 20);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
// Create an FBO and a texture region. // Create an FBO and a texture region.
fbo = new FrameBuffer(Pixmap.Format.RGBA8888, 512, 512, false); fbo = new FrameBuffer(Pixmap.Format.RGBA8888, 512, 512, false);
@ -104,7 +105,7 @@ public class PngExportTest extends ApplicationAdapter {
int frame = 1; int frame = 1;
while (time < animation.getDuration()) { while (time < animation.getDuration()) {
animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in); animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
// Render the skeleton to the FBO. // Render the skeleton to the FBO.
ScreenUtils.clear(0, 0, 0, 0); ScreenUtils.clear(0, 0, 0, 0);

View File

@ -36,6 +36,7 @@ import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** Demonstrates loading, animating, and rendering a skeleton. /** Demonstrates loading, animating, and rendering a skeleton.
@ -86,7 +87,7 @@ public class SimpleTest1 extends ApplicationAdapter {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
state.apply(skeleton); // Poses skeleton using current animations. This sets the bones' local SRT. state.apply(skeleton); // Poses skeleton using current animations. This sets the bones' local SRT.
skeleton.updateWorldTransform(); // Uses the bones' local SRT to compute their world SRT. skeleton.updateWorldTransform(Physics.update); // Uses the bones' local SRT to compute their world SRT.
// Configure the camera, SpriteBatch, and SkeletonRendererDebug. // Configure the camera, SpriteBatch, and SkeletonRendererDebug.
camera.update(); camera.update();

View File

@ -41,6 +41,7 @@ import com.badlogic.gdx.math.Vector3;
import com.esotericsoftware.spine.AnimationState.AnimationStateListener; import com.esotericsoftware.spine.AnimationState.AnimationStateListener;
import com.esotericsoftware.spine.AnimationState.TrackEntry; import com.esotericsoftware.spine.AnimationState.TrackEntry;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment; import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
@ -147,7 +148,7 @@ public class SimpleTest2 extends ApplicationAdapter {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
if (state.apply(skeleton)) // Poses skeleton using current animations. This sets the bones' local SRT. if (state.apply(skeleton)) // Poses skeleton using current animations. This sets the bones' local SRT.
skeleton.updateWorldTransform(); // Uses the bones' local SRT to compute their world SRT. skeleton.updateWorldTransform(Physics.update); // Uses the bones' local SRT to compute their world SRT.
// Configure the camera, SpriteBatch, and SkeletonRendererDebug. // Configure the camera, SpriteBatch, and SkeletonRendererDebug.
camera.update(); camera.update();

View File

@ -37,6 +37,8 @@ import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Demonstrates applying multiple animations at once using {@link AnimationState} tracks. */ /** Demonstrates applying multiple animations at once using {@link AnimationState} tracks. */
public class SimpleTest3 extends ApplicationAdapter { public class SimpleTest3 extends ApplicationAdapter {
OrthographicCamera camera; OrthographicCamera camera;
@ -84,7 +86,7 @@ public class SimpleTest3 extends ApplicationAdapter {
ScreenUtils.clear(0, 0, 0, 0); ScreenUtils.clear(0, 0, 0, 0);
if (state.apply(skeleton)) // Poses skeleton using current animations. This sets the bones' local SRT. if (state.apply(skeleton)) // Poses skeleton using current animations. This sets the bones' local SRT.
skeleton.updateWorldTransform(); // Uses the bones' local SRT to compute their world SRT. skeleton.updateWorldTransform(Physics.update); // Uses the bones' local SRT to compute their world SRT.
// Configure the camera, SpriteBatch, and SkeletonRendererDebug. // Configure the camera, SpriteBatch, and SkeletonRendererDebug.
camera.update(); camera.update();

View File

@ -38,6 +38,7 @@ import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.SkeletonDataLoader; import com.esotericsoftware.spine.utils.SkeletonDataLoader;
import com.esotericsoftware.spine.utils.SkeletonDataLoader.SkeletonDataParameter; import com.esotericsoftware.spine.utils.SkeletonDataLoader.SkeletonDataParameter;
@ -102,7 +103,7 @@ public class SkeletonAssetManagerTest extends ApplicationAdapter {
state.update(Gdx.graphics.getDeltaTime()); // Update the animation time. state.update(Gdx.graphics.getDeltaTime()); // Update the animation time.
state.apply(skeleton); // Poses skeleton using current animations. This sets the bones' local SRT. state.apply(skeleton); // Poses skeleton using current animations. This sets the bones' local SRT.
skeleton.updateWorldTransform(); // Uses the bones' local SRT to compute their world SRT. skeleton.updateWorldTransform(Physics.update); // Uses the bones' local SRT to compute their world SRT.
// Configure the camera, SpriteBatch, and SkeletonRendererDebug. // Configure the camera, SpriteBatch, and SkeletonRendererDebug.
camera.update(); camera.update();

View File

@ -37,6 +37,7 @@ import com.badlogic.gdx.graphics.g2d.PolygonSpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas; import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.attachments.SkeletonAttachment; import com.esotericsoftware.spine.attachments.SkeletonAttachment;
/** Demonstrates using {@link SkeletonAttachment} to use an entire skeleton as an attachment. */ /** Demonstrates using {@link SkeletonAttachment} to use an entire skeleton as an attachment. */
@ -93,11 +94,11 @@ public class SkeletonAttachmentTest extends ApplicationAdapter {
public void render () { public void render () {
spineboyState.update(Gdx.graphics.getDeltaTime()); spineboyState.update(Gdx.graphics.getDeltaTime());
spineboyState.apply(spineboy); spineboyState.apply(spineboy);
spineboy.updateWorldTransform(); spineboy.updateWorldTransform(Physics.update);
goblinState.update(Gdx.graphics.getDeltaTime()); goblinState.update(Gdx.graphics.getDeltaTime());
goblinState.apply(goblin); goblinState.apply(goblin);
goblin.updateWorldTransform(attachmentBone); goblin.updateWorldTransform(Physics.update, attachmentBone);
ScreenUtils.clear(0, 0, 0, 0); ScreenUtils.clear(0, 0, 0, 0);

View File

@ -39,6 +39,8 @@ import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.utils.ScreenUtils; import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Boilerplate for basic skeleton rendering, used for various testing. */ /** Boilerplate for basic skeleton rendering, used for various testing. */
public class TestHarness extends ApplicationAdapter { public class TestHarness extends ApplicationAdapter {
// static String JSON = "coin/coin-pro.json"; // static String JSON = "coin/coin-pro.json";
@ -79,7 +81,7 @@ public class TestHarness extends ApplicationAdapter {
// state.setAnimation(0, "rotate", false); // state.setAnimation(0, "rotate", false);
state.update(0); state.update(0);
state.apply(skeleton); state.apply(skeleton);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
} }
public void render () { public void render () {
@ -87,7 +89,7 @@ public class TestHarness extends ApplicationAdapter {
state.update(0.25f); // Update the animation time. state.update(0.25f); // Update the animation time.
} }
state.apply(skeleton); // Poses skeleton using current animations. This sets the bones' local SRT. state.apply(skeleton); // Poses skeleton using current animations. This sets the bones' local SRT.
skeleton.updateWorldTransform(); // Uses the bones' local SRT to compute their world SRT. skeleton.updateWorldTransform(Physics.update); // Uses the bones' local SRT to compute their world SRT.
ScreenUtils.clear(0, 0, 0, 0); ScreenUtils.clear(0, 0, 0, 0);

View File

@ -39,6 +39,7 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.Animation.MixDirection; import com.esotericsoftware.spine.Animation.MixDirection;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Demonstrates using the timeline API. See {@link SimpleTest1} for a higher level API using {@link AnimationState}. /** Demonstrates using the timeline API. See {@link SimpleTest1} for a higher level API using {@link AnimationState}.
* <p> * <p>
@ -79,7 +80,7 @@ public class TimelineApiTest extends ApplicationAdapter {
jumpAnimation = skeletonData.findAnimation("jump"); jumpAnimation = skeletonData.findAnimation("jump");
skeleton = new Skeleton(skeletonData); skeleton = new Skeleton(skeletonData);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
skeleton.setPosition(-50, 20); skeleton.setPosition(-50, 20);
} }
@ -127,7 +128,7 @@ public class TimelineApiTest extends ApplicationAdapter {
walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in); walkAnimation.apply(skeleton, time, time, true, events, 1, MixBlend.first, MixDirection.in);
} }
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
batch.begin(); batch.begin();
renderer.draw(batch, skeleton); renderer.draw(batch, skeleton);

View File

@ -38,6 +38,7 @@ import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.BoneData.TransformMode; import com.esotericsoftware.spine.BoneData.TransformMode;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Stores a bone's current pose. /** Stores a bone's current pose.
* <p> * <p>
@ -82,7 +83,7 @@ public class Bone implements Updatable {
} }
/** Computes the world transform using the parent bone and this bone's local applied transform. */ /** Computes the world transform using the parent bone and this bone's local applied transform. */
public void update () { public void update (Physics physics) {
updateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY); updateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY);
} }
@ -618,8 +619,8 @@ public class Bone implements Updatable {
/** Rotates the world transform the specified amount. /** Rotates the world transform the specified amount.
* <p> * <p>
* After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and {@link #update()} will * After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and
* need to be called on any child bones, recursively. */ * {@link #update(Physics)} will need to be called on any child bones, recursively. */
public void rotateWorld (float degrees) { public void rotateWorld (float degrees) {
degrees *= degRad; degrees *= degRad;
float sin = sin(degrees), cos = cos(degrees); float sin = sin(degrees), cos = cos(degrees);

View File

@ -32,6 +32,8 @@ package com.esotericsoftware.spine;
import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.Null; import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Stores the setup pose for a {@link Bone}. */ /** Stores the setup pose for a {@link Bone}. */
public class BoneData { public class BoneData {
final int index; final int index;
@ -175,8 +177,8 @@ public class BoneData {
this.transformMode = transformMode; this.transformMode = transformMode;
} }
/** When true, {@link Skeleton#updateWorldTransform()} only updates this bone if the {@link Skeleton#getSkin()} contains this /** When true, {@link Skeleton#updateWorldTransform(Physics)} only updates this bone if the {@link Skeleton#getSkin()} contains
* bone. * this bone.
* <p> * <p>
* See {@link Skin#getBones()}. */ * See {@link Skin#getBones()}. */
public boolean getSkinRequired () { public boolean getSkinRequired () {

View File

@ -29,6 +29,8 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
import com.esotericsoftware.spine.Skeleton.Physics;
/** The base class for all constraint datas. */ /** The base class for all constraint datas. */
abstract public class ConstraintData { abstract public class ConstraintData {
final String name; final String name;
@ -46,7 +48,7 @@ abstract public class ConstraintData {
} }
/** The ordinal of this constraint for the order a skeleton's constraints will be applied by /** The ordinal of this constraint for the order a skeleton's constraints will be applied by
* {@link Skeleton#updateWorldTransform()}. */ * {@link Skeleton#updateWorldTransform(Physics)}. */
public int getOrder () { public int getOrder () {
return order; return order;
} }
@ -55,8 +57,8 @@ abstract public class ConstraintData {
this.order = order; this.order = order;
} }
/** When true, {@link Skeleton#updateWorldTransform()} only updates this constraint if the {@link Skeleton#getSkin()} contains /** When true, {@link Skeleton#updateWorldTransform(Physics)} only updates this constraint if the {@link Skeleton#getSkin()}
* this constraint. * contains this constraint.
* <p> * <p>
* See {@link Skin#getConstraints()}. */ * See {@link Skin#getConstraints()}. */
public boolean getSkinRequired () { public boolean getSkinRequired () {

View File

@ -33,6 +33,8 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of /** Stores the current pose for an IK constraint. An IK constraint adjusts the rotation of 1 or 2 constrained bones so the tip of
* the last bone is as close to the target bone as possible. * the last bone is as close to the target bone as possible.
* <p> * <p>
@ -87,7 +89,7 @@ public class IkConstraint implements Updatable {
} }
/** Applies the constraint to the constrained bones. */ /** Applies the constraint to the constrained bones. */
public void update () { public void update (Physics physics) {
if (mix == 0) return; if (mix == 0) return;
Bone target = this.target; Bone target = this.target;
Object[] bones = this.bones.items; Object[] bones = this.bones.items;

View File

@ -39,6 +39,7 @@ import com.badlogic.gdx.utils.FloatArray;
import com.esotericsoftware.spine.PathConstraintData.PositionMode; import com.esotericsoftware.spine.PathConstraintData.PositionMode;
import com.esotericsoftware.spine.PathConstraintData.RotateMode; import com.esotericsoftware.spine.PathConstraintData.RotateMode;
import com.esotericsoftware.spine.PathConstraintData.SpacingMode; import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.attachments.Attachment; import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.PathAttachment; import com.esotericsoftware.spine.attachments.PathAttachment;
@ -101,7 +102,7 @@ public class PathConstraint implements Updatable {
} }
/** Applies the constraint to the constrained bones. */ /** Applies the constraint to the constrained bones. */
public void update () { public void update (Physics physics) {
Attachment attachment = target.attachment; Attachment attachment = target.attachment;
if (!(attachment instanceof PathAttachment)) return; if (!(attachment instanceof PathAttachment)) return;

View File

@ -29,14 +29,23 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
import static com.badlogic.gdx.math.Interpolation.*;
import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.Skeleton.Physics;
// BOZO - Physics steps/something in metrics view.
/** Stores the current pose for a physics constraint. A physics constraint applies physics to bones. /** Stores the current pose for a physics constraint. A physics constraint applies physics to bones.
* <p> * <p>
* See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */ * See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
public class PhysicsConstraint implements Updatable { public class PhysicsConstraint implements Updatable {
final PhysicsConstraintData data; final PhysicsConstraintData data;
final Array<Bone> bones; final Array<Bone> bones;
State[] states = {};
float mix; float mix;
boolean active; boolean active;
@ -66,19 +75,158 @@ public class PhysicsConstraint implements Updatable {
mix = constraint.mix; mix = constraint.mix;
} }
public void setToSetupPose () { /** Caches information about bones. Must be called if {@link PathConstraintData#getBones()} is modified. */
public void updateBones () {
int count = 0;
if (data.x) count = 1;
if (data.y) count++;
if (data.rotate) count++;
if (data.scaleX) count++;
if (data.shearX) count++;
count *= bones.size * 2;
if (states.length != count) {
states = new State[count];
for (int i = 0; i < count; i++)
states[i] = new State();
}
}
public void reset () {
remaining = 0; remaining = 0;
lastTime = skeleton.time; lastTime = skeleton.time;
for (int i = 0, n = states.length; i < n; i++) {
State state = states[i];
state.last = false;
state.offset = 0;
state.velocity = 0;
}
}
public void setToSetupPose () {
reset();
PhysicsConstraintData data = this.data; PhysicsConstraintData data = this.data;
mix = data.mix; mix = data.mix;
} }
/** Applies the constraint to the constrained bones. */ /** Applies the constraint to the constrained bones. */
public void update () { public void update (Physics physics) {
if (mix == 0) return; if (mix == 0) return;
// BOZO data.rotate = true; // BOZO - Remove.
updateBones(); // BOZO - Remove.
Object[] bones = this.bones.items;
int boneCount = this.bones.size;
switch (physics) {
case none:
return;
case reset:
reset();
// Fall through.
case update:
for (int i = 0; i < boneCount; i++) {
Bone bone = (Bone)bones[i];
if (data.rotate) {
Vector2 tip = bone.localToWorld(new Vector2(bone.data.length, 0));
State state = states[i];
if (!state.last)
state.last = true;
else if (state.x != bone.worldX || state.y != bone.worldY) {
float angleToOldTip = new Vector2(state.tipx, state.tipy).sub(bone.worldX, bone.worldY).angleDeg()
+ state.offset - bone.getWorldRotationX();
angleToOldTip -= (16384 - (int)(16384.499999999996 - angleToOldTip / 360)) * 360;
state.offset = linear.apply(0, angleToOldTip, data.inertia);
// if (angleToOldTip > 0.0001f || angleToOldTip < -0.0001f) //
// System.out.println(angleToOldTip);
// if (applyShear) {
// if (rotationOffset > 0)
// rotationOffset = Math.max(0, rotationOffset - shearOffset);
// else
// rotationOffset = Math.min(0, rotationOffset - shearOffset);
// }
}
tip = bone.localToWorld(new Vector2(bone.data.length, 0));
// if (bone.worldX!=271.64316f)
// System.out.println(bone.worldX);
if (bone.worldY != 662.5888f) System.out.println(bone.worldY);
// System.out.println(bone.worldY);
state.x = bone.worldX;
state.y = bone.worldY;
state.tipx = tip.x;
state.tipy = tip.y;
}
// BOZO - Update physics x, y, scaleX, shearX.
}
}
boolean angle = data.rotate || data.scaleX || data.shearX;
remaining += Math.max(skeleton.time - lastTime, 0);
lastTime = skeleton.time;
float step = 0.016f; // BOZO - Keep fixed step? Make it configurable?
float cos = 0, sin = 0;
while (remaining >= step) {
remaining -= step;
for (int i = 0; i < boneCount; i++) {
Bone bone = (Bone)bones[i];
if (angle) {
float r = bone.getWorldRotationX() * degRad;
cos = (float)Math.cos(r);
sin = (float)Math.sin(r);
}
if (data.rotate) {
State state = states[i];
// BOZO - Keep length affecting rotation? Calculate world length?
float windForce = bone.data.length * 0.5f * (-data.wind * sin - data.gravity * cos);
float springForce = state.offset * data.strength;
float frictionForce = data.friction * state.velocity;
state.velocity += (windForce - springForce - frictionForce) / data.mass;
state.offset += state.velocity * step;
state.velocity *= data.damping;
}
}
}
if (mix == 1) {
for (int i = 0; i < boneCount; i++) {
Bone bone = (Bone)bones[i];
if (angle) {
float r = bone.getWorldRotationX() * degRad;
cos = (float)Math.cos(r);
sin = (float)Math.sin(r);
}
if (data.rotate) {
State state = states[i];
bone.rotateWorld(state.offset);
bone.updateAppliedTransform();
}
}
} else {
// BOZO - PhysicsConstraint mix.
}
}
public void step () {
// BOZO - PhysicsConstraint#step.
}
/** The bones that will be modified by this physics constraint. */
public Array<Bone> getBones () {
return bones;
}
/** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */
public float getMix () {
return mix;
}
public void setMix (float mix) {
this.mix = mix;
} }
public boolean isActive () { public boolean isActive () {
@ -93,4 +241,9 @@ public class PhysicsConstraint implements Updatable {
public String toString () { public String toString () {
return data.name; return data.name;
} }
static class State {
boolean last;
float x, y, tipx, tipy, offset, velocity;
}
} }

View File

@ -36,9 +36,9 @@ import com.badlogic.gdx.utils.Array;
* See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */ * See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
public class PhysicsConstraintData extends ConstraintData { public class PhysicsConstraintData extends ConstraintData {
final Array<BoneData> bones = new Array(); final Array<BoneData> bones = new Array();
float speed = 1, mass = 1; float speed = 1, mass = 1; // BOZO - Keep speed?
float strength, friction, damping, inertia, wind, gravity, mix; float strength, friction, damping, inertia, wind, gravity, mix;
boolean translate, rotate, scale, shear; boolean x, y, rotate, scaleX, shearX;
public PhysicsConstraintData (String name) { public PhysicsConstraintData (String name) {
super(name); super(name);
@ -113,12 +113,20 @@ public class PhysicsConstraintData extends ConstraintData {
this.gravity = gravity; this.gravity = gravity;
} }
public boolean getTranslate () { public boolean getX () {
return translate; return x;
} }
public void setTranslate (boolean translate) { public void setX (boolean x) {
this.translate = translate; this.x = x;
}
public boolean getY () {
return y;
}
public void setY (boolean y) {
this.y = y;
} }
public boolean getRotate () { public boolean getRotate () {
@ -129,20 +137,20 @@ public class PhysicsConstraintData extends ConstraintData {
this.rotate = rotate; this.rotate = rotate;
} }
public boolean getScale () { public boolean getScaleX () {
return scale; return scaleX;
} }
public void setScale (boolean scale) { public void setScaleX (boolean scaleX) {
this.scale = scale; this.scaleX = scaleX;
} }
public boolean getShear () { public boolean getShearX () {
return shear; return shearX;
} }
public void setShear (boolean shear) { public void setShearX (boolean shearX) {
this.shear = shear; this.shearX = shearX;
} }
/** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */ /** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */

View File

@ -384,7 +384,7 @@ public class Skeleton {
* <p> * <p>
* See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine * See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide. */ * Runtimes Guide. */
public void updateWorldTransform () { public void updateWorldTransform (Physics physics) {
Object[] bones = this.bones.items; Object[] bones = this.bones.items;
for (int i = 0, n = this.bones.size; i < n; i++) { for (int i = 0, n = this.bones.size; i < n; i++) {
Bone bone = (Bone)bones[i]; Bone bone = (Bone)bones[i];
@ -399,7 +399,7 @@ public class Skeleton {
Object[] updateCache = this.updateCache.items; Object[] updateCache = this.updateCache.items;
for (int i = 0, n = this.updateCache.size; i < n; i++) for (int i = 0, n = this.updateCache.size; i < n; i++)
((Updatable)updateCache[i]).update(); ((Updatable)updateCache[i]).update(physics);
} }
/** Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies /** Temporarily sets the root bone as a child of the specified bone, then updates the world transform for each bone and applies
@ -407,7 +407,7 @@ public class Skeleton {
* <p> * <p>
* See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine * See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide. */ * Runtimes Guide. */
public void updateWorldTransform (Bone parent) { public void updateWorldTransform (Physics physics, Bone parent) {
if (parent == null) throw new IllegalArgumentException("parent cannot be null."); if (parent == null) throw new IllegalArgumentException("parent cannot be null.");
Object[] bones = this.bones.items; Object[] bones = this.bones.items;
@ -443,7 +443,7 @@ public class Skeleton {
Object[] updateCache = this.updateCache.items; Object[] updateCache = this.updateCache.items;
for (int i = 0, n = this.updateCache.size; i < n; i++) { for (int i = 0, n = this.updateCache.size; i < n; i++) {
Updatable updatable = (Updatable)updateCache[i]; Updatable updatable = (Updatable)updateCache[i];
if (updatable != rootBone) updatable.update(); if (updatable != rootBone) updatable.update(physics);
} }
} }
@ -833,4 +833,19 @@ public class Skeleton {
public String toString () { public String toString () {
return data.name != null ? data.name : super.toString(); return data.name != null ? data.name : super.toString();
} }
/** Determines how physics and other non-deterministic updates are applied. */
static public enum Physics {
/** Physics are not updated or applied. */
none,
/** Physics are not updated but the pose from physics is applied. */
pose,
/** Physics are updated and the pose from physics is applied. */
update,
/** Physics are reset to the current pose. */
reset
}
} }

View File

@ -34,6 +34,8 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*;
import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Array;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained /** Stores the current pose for a transform constraint. A transform constraint adjusts the world transform of the constrained
* bones to match that of the target bone. * bones to match that of the target bone.
* <p> * <p>
@ -90,7 +92,7 @@ public class TransformConstraint implements Updatable {
} }
/** Applies the constraint to the constrained bones. */ /** Applies the constraint to the constrained bones. */
public void update () { public void update (Physics physics) {
if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleY == 0 && mixShearY == 0) return; if (mixRotate == 0 && mixX == 0 && mixY == 0 && mixScaleX == 0 && mixScaleY == 0 && mixShearY == 0) return;
if (data.local) { if (data.local) {
if (data.relative) if (data.relative)

View File

@ -29,9 +29,12 @@
package com.esotericsoftware.spine; package com.esotericsoftware.spine;
/** The interface for items updated by {@link Skeleton#updateWorldTransform()}. */ import com.esotericsoftware.spine.Skeleton.Physics;
/** The interface for items updated by {@link Skeleton#updateWorldTransform(Physics)}. */
public interface Updatable { public interface Updatable {
public void update (); /** @param physics Determines how physics and other non-deterministic updates are applied. */
public void update (Physics physics);
/** Returns false when this item has not been updated because a skin is required and the {@link Skeleton#getSkin() active skin} /** Returns false when this item has not been updated because a skin is required and the {@link Skeleton#getSkin() active skin}
* does not contain this item. * does not contain this item.

View File

@ -52,6 +52,7 @@ import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.esotericsoftware.spine.Animation.MixBlend; import com.esotericsoftware.spine.Animation.MixBlend;
import com.esotericsoftware.spine.AnimationState.AnimationStateAdapter; import com.esotericsoftware.spine.AnimationState.AnimationStateAdapter;
import com.esotericsoftware.spine.AnimationState.TrackEntry; import com.esotericsoftware.spine.AnimationState.TrackEntry;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch; import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
import java.awt.Toolkit; import java.awt.Toolkit;
@ -154,10 +155,7 @@ public class SkeletonViewer extends ApplicationAdapter {
} }
skeleton = new Skeleton(skeletonData); skeleton = new Skeleton(skeletonData);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
skeleton.setToSetupPose();
skeleton = new Skeleton(skeleton); // Tests copy constructors.
skeleton.updateWorldTransform();
state = new AnimationState(new AnimationStateData(skeletonData)); state = new AnimationState(new AnimationStateData(skeletonData));
state.addListener(new AnimationStateAdapter() { state.addListener(new AnimationStateAdapter() {
@ -269,7 +267,7 @@ public class SkeletonViewer extends ApplicationAdapter {
delta = Math.min(delta, 0.032f) * ui.speedSlider.getValue(); delta = Math.min(delta, 0.032f) * ui.speedSlider.getValue();
state.update(delta); state.update(delta);
state.apply(skeleton); state.apply(skeleton);
skeleton.updateWorldTransform(); skeleton.updateWorldTransform(Physics.update);
batch.begin(); batch.begin();
renderer.draw(batch, skeleton); renderer.draw(batch, skeleton);