[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.MixDirection;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.attachments.AttachmentLoader;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.attachments.ClippingAttachment;
@ -83,7 +84,7 @@ public class BonePlotting {
float time = 0;
while (time < animation.getDuration()) {
animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in);
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
System.out.println(animation.getName() + "," //
+ 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.MixDirection;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.attachments.AtlasAttachmentLoader;
import com.esotericsoftware.spine.attachments.RegionAttachment;
import com.esotericsoftware.spine.attachments.Sequence;
@ -104,7 +105,7 @@ public class Box2DExample extends ApplicationAdapter {
skeleton = new Skeleton(skeletonData);
skeleton.x = -32;
skeleton.y = 1;
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
// See Box2DTest in libgdx for more detailed information about Box2D setup.
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);
skeleton.x += 8 * delta;
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
skeletonRenderer.draw(batch, skeleton);
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.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** 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.
skeleton = new Skeleton(skeletonData);
skeleton.setPosition(250, 20);
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
// Create an FBO and a texture region with Y flipped.
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.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
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
@ -97,7 +98,7 @@ public class IKTest extends ApplicationAdapter {
// later.
state.update(Gdx.graphics.getDeltaTime());
state.apply(skeleton);
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
// Position the "crosshair" bone at the mouse
// location. We do this before calling
@ -122,7 +123,7 @@ public class IKTest extends ApplicationAdapter {
// Calculate final world transform with the
// crosshair bone set to the mouse cursor
// position.
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
// Clear the screen, update the camera and
// 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.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Demonstrates creating and configuring a new skin at runtime. */
public class MixAndMatchTest extends ApplicationAdapter {
OrthographicCamera camera;
@ -92,7 +94,7 @@ public class MixAndMatchTest extends ApplicationAdapter {
ScreenUtils.clear(0, 0, 0, 0);
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
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.MixDirection;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Demonstrates simplistic usage of lighting with normal maps.
* <p>
@ -110,7 +111,7 @@ public class NormalMapTest extends ApplicationAdapter {
skeleton = new Skeleton(skeleton);
skeleton.setX(ui.prefs.getFloat("x", Gdx.graphics.getWidth() / 2));
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() {
public boolean touchDown (int screenX, int screenY, int pointer, int button) {
@ -136,7 +137,7 @@ public class NormalMapTest extends ApplicationAdapter {
float lastTime = time;
time += Gdx.graphics.getDeltaTime();
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.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.MixDirection;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** 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.
skeleton = new Skeleton(skeletonData);
skeleton.setPosition(250, 20);
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
// Create an FBO and a texture region.
fbo = new FrameBuffer(Pixmap.Format.RGBA8888, 512, 512, false);
@ -104,7 +105,7 @@ public class PngExportTest extends ApplicationAdapter {
int frame = 1;
while (time < animation.getDuration()) {
animation.apply(skeleton, time, time, false, null, 1, MixBlend.first, MixDirection.in);
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
// Render the skeleton to the FBO.
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.g2d.TextureAtlas;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
/** Demonstrates loading, animating, and rendering a skeleton.
@ -86,7 +87,7 @@ public class SimpleTest1 extends ApplicationAdapter {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
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.
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.TrackEntry;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.attachments.BoundingBoxAttachment;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
@ -147,7 +148,7 @@ public class SimpleTest2 extends ApplicationAdapter {
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
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.
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.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Demonstrates applying multiple animations at once using {@link AnimationState} tracks. */
public class SimpleTest3 extends ApplicationAdapter {
OrthographicCamera camera;
@ -84,7 +86,7 @@ public class SimpleTest3 extends ApplicationAdapter {
ScreenUtils.clear(0, 0, 0, 0);
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.
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.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.SkeletonDataLoader;
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.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.
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.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.attachments.SkeletonAttachment;
/** Demonstrates using {@link SkeletonAttachment} to use an entire skeleton as an attachment. */
@ -93,11 +94,11 @@ public class SkeletonAttachmentTest extends ApplicationAdapter {
public void render () {
spineboyState.update(Gdx.graphics.getDeltaTime());
spineboyState.apply(spineboy);
spineboy.updateWorldTransform();
spineboy.updateWorldTransform(Physics.update);
goblinState.update(Gdx.graphics.getDeltaTime());
goblinState.apply(goblin);
goblin.updateWorldTransform(attachmentBone);
goblin.updateWorldTransform(Physics.update, attachmentBone);
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.utils.ScreenUtils;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Boilerplate for basic skeleton rendering, used for various testing. */
public class TestHarness extends ApplicationAdapter {
// static String JSON = "coin/coin-pro.json";
@ -79,7 +81,7 @@ public class TestHarness extends ApplicationAdapter {
// state.setAnimation(0, "rotate", false);
state.update(0);
state.apply(skeleton);
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
}
public void render () {
@ -87,7 +89,7 @@ public class TestHarness extends ApplicationAdapter {
state.update(0.25f); // Update the animation time.
}
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);

View File

@ -39,6 +39,7 @@ import com.badlogic.gdx.utils.ScreenUtils;
import com.esotericsoftware.spine.Animation.MixBlend;
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}.
* <p>
@ -79,7 +80,7 @@ public class TimelineApiTest extends ApplicationAdapter {
jumpAnimation = skeletonData.findAnimation("jump");
skeleton = new Skeleton(skeletonData);
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
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);
}
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
batch.begin();
renderer.draw(batch, skeleton);

View File

@ -38,6 +38,7 @@ import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Null;
import com.esotericsoftware.spine.BoneData.TransformMode;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Stores a bone's current pose.
* <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. */
public void update () {
public void update (Physics physics) {
updateWorldTransform(ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY);
}
@ -618,8 +619,8 @@ public class Bone implements Updatable {
/** Rotates the world transform the specified amount.
* <p>
* After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and {@link #update()} will
* need to be called on any child bones, recursively. */
* After changes are made to the world transform, {@link #updateAppliedTransform()} should be called and
* {@link #update(Physics)} will need to be called on any child bones, recursively. */
public void rotateWorld (float degrees) {
degrees *= degRad;
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.utils.Null;
import com.esotericsoftware.spine.Skeleton.Physics;
/** Stores the setup pose for a {@link Bone}. */
public class BoneData {
final int index;
@ -175,8 +177,8 @@ public class BoneData {
this.transformMode = transformMode;
}
/** When true, {@link Skeleton#updateWorldTransform()} only updates this bone if the {@link Skeleton#getSkin()} contains this
* bone.
/** When true, {@link Skeleton#updateWorldTransform(Physics)} only updates this bone if the {@link Skeleton#getSkin()} contains
* this bone.
* <p>
* See {@link Skin#getBones()}. */
public boolean getSkinRequired () {

View File

@ -29,6 +29,8 @@
package com.esotericsoftware.spine;
import com.esotericsoftware.spine.Skeleton.Physics;
/** The base class for all constraint datas. */
abstract public class ConstraintData {
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
* {@link Skeleton#updateWorldTransform()}. */
* {@link Skeleton#updateWorldTransform(Physics)}. */
public int getOrder () {
return order;
}
@ -55,8 +57,8 @@ abstract public class ConstraintData {
this.order = order;
}
/** When true, {@link Skeleton#updateWorldTransform()} only updates this constraint if the {@link Skeleton#getSkin()} contains
* this constraint.
/** When true, {@link Skeleton#updateWorldTransform(Physics)} only updates this constraint if the {@link Skeleton#getSkin()}
* contains this constraint.
* <p>
* See {@link Skin#getConstraints()}. */
public boolean getSkinRequired () {

View File

@ -33,6 +33,8 @@ import static com.esotericsoftware.spine.utils.SpineUtils.*;
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
* the last bone is as close to the target bone as possible.
* <p>
@ -87,7 +89,7 @@ public class IkConstraint implements Updatable {
}
/** Applies the constraint to the constrained bones. */
public void update () {
public void update (Physics physics) {
if (mix == 0) return;
Bone target = this.target;
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.RotateMode;
import com.esotericsoftware.spine.PathConstraintData.SpacingMode;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.attachments.Attachment;
import com.esotericsoftware.spine.attachments.PathAttachment;
@ -101,7 +102,7 @@ public class PathConstraint implements Updatable {
}
/** Applies the constraint to the constrained bones. */
public void update () {
public void update (Physics physics) {
Attachment attachment = target.attachment;
if (!(attachment instanceof PathAttachment)) return;

View File

@ -29,14 +29,23 @@
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.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.
* <p>
* See <a href="http://esotericsoftware.com/spine-physics-constraints">Physics constraints</a> in the Spine User Guide. */
public class PhysicsConstraint implements Updatable {
final PhysicsConstraintData data;
final Array<Bone> bones;
State[] states = {};
float mix;
boolean active;
@ -66,19 +75,158 @@ public class PhysicsConstraint implements Updatable {
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;
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;
mix = data.mix;
}
/** Applies the constraint to the constrained bones. */
public void update () {
public void update (Physics physics) {
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 () {
@ -93,4 +241,9 @@ public class PhysicsConstraint implements Updatable {
public String toString () {
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. */
public class PhysicsConstraintData extends ConstraintData {
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;
boolean translate, rotate, scale, shear;
boolean x, y, rotate, scaleX, shearX;
public PhysicsConstraintData (String name) {
super(name);
@ -113,12 +113,20 @@ public class PhysicsConstraintData extends ConstraintData {
this.gravity = gravity;
}
public boolean getTranslate () {
return translate;
public boolean getX () {
return x;
}
public void setTranslate (boolean translate) {
this.translate = translate;
public void setX (boolean x) {
this.x = x;
}
public boolean getY () {
return y;
}
public void setY (boolean y) {
this.y = y;
}
public boolean getRotate () {
@ -129,20 +137,20 @@ public class PhysicsConstraintData extends ConstraintData {
this.rotate = rotate;
}
public boolean getScale () {
return scale;
public boolean getScaleX () {
return scaleX;
}
public void setScale (boolean scale) {
this.scale = scale;
public void setScaleX (boolean scaleX) {
this.scaleX = scaleX;
}
public boolean getShear () {
return shear;
public boolean getShearX () {
return shearX;
}
public void setShear (boolean shear) {
this.shear = shear;
public void setShearX (boolean shearX) {
this.shearX = shearX;
}
/** A percentage (0-1) that controls the mix between the constrained and unconstrained poses. */

View File

@ -384,7 +384,7 @@ public class Skeleton {
* <p>
* See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide. */
public void updateWorldTransform () {
public void updateWorldTransform (Physics physics) {
Object[] bones = this.bones.items;
for (int i = 0, n = this.bones.size; i < n; i++) {
Bone bone = (Bone)bones[i];
@ -399,7 +399,7 @@ public class Skeleton {
Object[] updateCache = this.updateCache.items;
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
@ -407,7 +407,7 @@ public class Skeleton {
* <p>
* See <a href="http://esotericsoftware.com/spine-runtime-skeletons#World-transforms">World transforms</a> in the Spine
* Runtimes Guide. */
public void updateWorldTransform (Bone parent) {
public void updateWorldTransform (Physics physics, Bone parent) {
if (parent == null) throw new IllegalArgumentException("parent cannot be null.");
Object[] bones = this.bones.items;
@ -443,7 +443,7 @@ public class Skeleton {
Object[] updateCache = this.updateCache.items;
for (int i = 0, n = this.updateCache.size; i < n; 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 () {
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.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
* bones to match that of the target bone.
* <p>
@ -90,7 +92,7 @@ public class TransformConstraint implements Updatable {
}
/** 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 (data.local) {
if (data.relative)

View File

@ -29,9 +29,12 @@
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 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}
* 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.AnimationState.AnimationStateAdapter;
import com.esotericsoftware.spine.AnimationState.TrackEntry;
import com.esotericsoftware.spine.Skeleton.Physics;
import com.esotericsoftware.spine.utils.TwoColorPolygonBatch;
import java.awt.Toolkit;
@ -154,10 +155,7 @@ public class SkeletonViewer extends ApplicationAdapter {
}
skeleton = new Skeleton(skeletonData);
skeleton.updateWorldTransform();
skeleton.setToSetupPose();
skeleton = new Skeleton(skeleton); // Tests copy constructors.
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
state = new AnimationState(new AnimationStateData(skeletonData));
state.addListener(new AnimationStateAdapter() {
@ -269,7 +267,7 @@ public class SkeletonViewer extends ApplicationAdapter {
delta = Math.min(delta, 0.032f) * ui.speedSlider.getValue();
state.update(delta);
state.apply(skeleton);
skeleton.updateWorldTransform();
skeleton.updateWorldTransform(Physics.update);
batch.begin();
renderer.draw(batch, skeleton);