rig-play 1.9.2 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +163 -6
- package/dist/index.js.map +1 -1
- package/dist/player.d.ts +44 -0
- package/dist/player.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -213,6 +213,87 @@ player.dispose();
|
|
|
213
213
|
assets.dispose();
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
+
### A minimal page
|
|
217
|
+
|
|
218
|
+
Layout is the caller's, so the first page is the place to see it: the root
|
|
219
|
+
element is the rig's world origin, Y up, and scaling it is a CSS transform the
|
|
220
|
+
player cannot see. This one loads a build, fits its stage box to a container,
|
|
221
|
+
tells the renderer about the scale, and plays the first animation:
|
|
222
|
+
|
|
223
|
+
```html
|
|
224
|
+
<!doctype html>
|
|
225
|
+
<meta charset="utf-8">
|
|
226
|
+
<div id="stage" style="position: relative; width: 480px; height: 640px; overflow: hidden"></div>
|
|
227
|
+
<script type="module">
|
|
228
|
+
import { loadModel, RigPlayer } from 'https://cdn.jsdelivr.net/npm/@rig-c/play@1/dist/index.js';
|
|
229
|
+
|
|
230
|
+
const stage = document.getElementById('stage');
|
|
231
|
+
const assets = await loadModel({ modelUrl: 'rigs/look/skeleton.model.json' });
|
|
232
|
+
const root = stage.appendChild(document.createElement('div'));
|
|
233
|
+
const player = new RigPlayer(root, assets);
|
|
234
|
+
player.play(assets.animations[0].name);
|
|
235
|
+
|
|
236
|
+
// Fit the rig's stage box (world units, Y up) to the container: scale it, and
|
|
237
|
+
// put the origin where the box's left edge and top land.
|
|
238
|
+
const box = assets.stage ?? player.bounds();
|
|
239
|
+
const scale = Math.min(stage.clientWidth / box.width, stage.clientHeight / box.height);
|
|
240
|
+
root.style.cssText = `position: absolute; transform-origin: 0 0; transform: scale(${scale});
|
|
241
|
+
left: ${-box.x * scale}px; top: ${(box.y + box.height) * scale}px`;
|
|
242
|
+
player.renderer.syncPixelRatio(); // raster at the size on screen, not the unscaled one
|
|
243
|
+
|
|
244
|
+
let last = performance.now();
|
|
245
|
+
requestAnimationFrame(function tick(now) {
|
|
246
|
+
player.frame((now - last) / 1000);
|
|
247
|
+
last = now;
|
|
248
|
+
requestAnimationFrame(tick);
|
|
249
|
+
});
|
|
250
|
+
</script>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
`syncPixelRatio()` is what makes the scale cheap: without it the mesh canvases
|
|
254
|
+
raster at the device pixel ratio of the *unscaled* rig, `1 / scale` times more
|
|
255
|
+
pixels than the screen shows. Call it again after anything that changes the
|
|
256
|
+
scale. `tests/readme-page.spec.ts` runs this block as written, against the
|
|
257
|
+
package built from this tree.
|
|
258
|
+
|
|
259
|
+
### Wind, gravity and dragging the rig
|
|
260
|
+
|
|
261
|
+
A rig's physics links answer the world the page sets (#121). `environment()`
|
|
262
|
+
gives them spine-core's skeleton-level wind and gravity — x right, **y down**,
|
|
263
|
+
the runtime's axes; each constraint's authored `wind` / `gravity` is its
|
|
264
|
+
response, so a link stating `wind: 0` feels none — and `moved()` tells them the
|
|
265
|
+
page moved or turned the rig, in the skeleton's world (y up), so a dragged rig
|
|
266
|
+
trails its cloth. Both apply from the next step, at any time. Added to the
|
|
267
|
+
minimal page above:
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
// A wind slider, -1 (left) to 1 (right). The runtime starts at wind [1, 0], so set calm first.
|
|
271
|
+
const wind = stage.appendChild(Object.assign(document.createElement('input'),
|
|
272
|
+
{ type: 'range', min: '-1', max: '1', step: '0.01', value: '0' }));
|
|
273
|
+
player.environment({ wind: [0, 0] });
|
|
274
|
+
wind.addEventListener('input', () => player.environment({ wind: [Number(wind.value), 0] }));
|
|
275
|
+
|
|
276
|
+
// Drag the rig: move the root, and tell the physics how far, in world units with y up.
|
|
277
|
+
let held = null; // the pointer's position while a drag is held
|
|
278
|
+
stage.addEventListener('pointerdown', (e) => { if (e.target !== wind) held = { x: e.clientX, y: e.clientY }; });
|
|
279
|
+
addEventListener('pointerup', () => { held = null; });
|
|
280
|
+
stage.addEventListener('pointermove', (e) => {
|
|
281
|
+
if (!held) return;
|
|
282
|
+
const dx = e.clientX - held.x, dy = e.clientY - held.y;
|
|
283
|
+
held = { x: e.clientX, y: e.clientY };
|
|
284
|
+
root.style.left = `${parseFloat(root.style.left) + dx}px`;
|
|
285
|
+
root.style.top = `${parseFloat(root.style.top) + dy}px`;
|
|
286
|
+
player.moved({ x: dx / scale, y: -dy / scale });
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The block is plain JavaScript, so it runs inside the page as written.
|
|
291
|
+
`environment(null)` returns to the runtime's start values, and `environment()`
|
|
292
|
+
reads what stands. The state is the player's, not a track's: every track and
|
|
293
|
+
layer answers the same world, and a `seek` replays under the environment as it
|
|
294
|
+
stands — no history is kept. A rig with no physics ignores both.
|
|
295
|
+
`tests/readme-page.spec.ts` runs this block in the minimal page.
|
|
296
|
+
|
|
216
297
|
The player owns one track: `play(name, { loop })` opens an animation at its first
|
|
217
298
|
pose, `frame(dt)` steps it (or `advance(dt)` then `draw()`, to time the two
|
|
218
299
|
apart), `pause()` / `resume()` hold and release it, `timeScale` scales every
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ export { type CanvasBackend, CanvasRenderer, type CanvasViewport } from './Canva
|
|
|
2
2
|
export { DomRenderer, type MeshBackend, type MeshSourceRect } from './DomRenderer.js';
|
|
3
3
|
export { type PageLayout, type PageRegion, type RegionEncoding, type RegionImage, revokeRegions, type UnpackRegionsOptions, unpackRegions } from './DomTexture.js';
|
|
4
4
|
export { type LoadModelOptions, loadModel, type ModelAssets, type ModelDocument, type RegionLoading } from './loadModel.js';
|
|
5
|
-
export { type AdjustableBones, type BoneAdjust, type BoneLocals, type BoneWorld, type PlayOptions, type PoseBounds, type QueueOptions, type RenderMode, type RigComplete, type RigEvent, RigPlayer, type RigPlayerOptions, type SlotFilter, type TrackEntry } from './player.js';
|
|
5
|
+
export { type AdjustableBones, type BoneAdjust, type BoneLocals, type BoneWorld, type PlayOptions, type PoseBounds, type QueueOptions, type RenderMode, type RigComplete, type RigEnvironment, type RigEvent, RigPlayer, type RigPlayerOptions, type SlotFilter, type TrackEntry } from './player.js';
|
|
6
6
|
export type { BlendMode, PosedClip, PosedFrame, PosedMesh, PosedRegion, PosedSlot, } from './posedFrame.js';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnK,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC5H,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC9F,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtF,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnK,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC5H,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACtS,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1741,6 +1741,7 @@ function slotBonePlan(bones, active, records, skipped) {
|
|
|
1741
1741
|
});
|
|
1742
1742
|
return plan;
|
|
1743
1743
|
}
|
|
1744
|
+
const RUNTIME_RAD = RUNTIME_PI / 180;
|
|
1744
1745
|
const PHYSICS_FIELDS = [
|
|
1745
1746
|
"bone",
|
|
1746
1747
|
"scaleY",
|
|
@@ -1949,6 +1950,30 @@ function resetPhysicsState(s, time) {
|
|
|
1949
1950
|
s.rotateOffset = s.rotateLag = s.rotateVelocity = 0;
|
|
1950
1951
|
s.scaleOffset = s.scaleLag = s.scaleVelocity = 0;
|
|
1951
1952
|
}
|
|
1953
|
+
const DEFAULT_PHYSICS_ENVIRONMENT = Object.freeze({ wind: Object.freeze([1, 0]), gravity: Object.freeze([0, 1]) });
|
|
1954
|
+
function translatePhysics(ctx, x, y) {
|
|
1955
|
+
for (const s of ctx.states.values()) {
|
|
1956
|
+
s.ux -= x;
|
|
1957
|
+
s.uy -= y;
|
|
1958
|
+
s.cx -= x;
|
|
1959
|
+
s.cy -= y;
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
function rotatePhysics(ctx, x, y, degrees) {
|
|
1963
|
+
const r = degrees * RUNTIME_RAD;
|
|
1964
|
+
const cos = Math.cos(r);
|
|
1965
|
+
const sin = Math.sin(r);
|
|
1966
|
+
for (const s of ctx.states.values()) {
|
|
1967
|
+
const dx = s.cx - x;
|
|
1968
|
+
const dy = s.cy - y;
|
|
1969
|
+
const tx = dx * cos - dy * sin - dx;
|
|
1970
|
+
const ty = dx * sin + dy * cos - dy;
|
|
1971
|
+
s.ux -= tx;
|
|
1972
|
+
s.uy -= ty;
|
|
1973
|
+
s.cx -= tx;
|
|
1974
|
+
s.cy -= ty;
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1952
1977
|
function physicsState(ctx, name) {
|
|
1953
1978
|
let s = ctx.states.get(name);
|
|
1954
1979
|
if (s === void 0) {
|
|
@@ -1975,6 +2000,7 @@ function stepPhysics(r, w, length, ctx) {
|
|
|
1975
2000
|
s.lastTime = ctx.time;
|
|
1976
2001
|
const bx = w.worldX;
|
|
1977
2002
|
const by = w.worldY;
|
|
2003
|
+
const env = ctx.env ?? DEFAULT_PHYSICS_ENVIRONMENT;
|
|
1978
2004
|
let z = 0;
|
|
1979
2005
|
if (s.pending) {
|
|
1980
2006
|
s.pending = false;
|
|
@@ -2005,16 +2031,18 @@ function stepPhysics(r, w, length, ctx) {
|
|
|
2005
2031
|
const e = r.strength;
|
|
2006
2032
|
const wind = r.wind * f;
|
|
2007
2033
|
const g = r.gravity * f;
|
|
2034
|
+
const ax = wind * env.wind[0] + g * env.gravity[0];
|
|
2035
|
+
const ay = wind * env.wind[1] + g * env.gravity[1];
|
|
2008
2036
|
const xs = s.xOffset;
|
|
2009
2037
|
const ys = s.yOffset;
|
|
2010
2038
|
do {
|
|
2011
2039
|
if (x) {
|
|
2012
|
-
s.xVelocity += (
|
|
2040
|
+
s.xVelocity += (ax - s.xOffset * e) * m;
|
|
2013
2041
|
s.xOffset += s.xVelocity * t;
|
|
2014
2042
|
s.xVelocity *= d;
|
|
2015
2043
|
}
|
|
2016
2044
|
if (y) {
|
|
2017
|
-
s.yVelocity -= (
|
|
2045
|
+
s.yVelocity -= (ay + s.yOffset * e) * m;
|
|
2018
2046
|
s.yOffset += s.yVelocity * t;
|
|
2019
2047
|
s.yVelocity *= d;
|
|
2020
2048
|
}
|
|
@@ -2061,20 +2089,20 @@ function stepPhysics(r, w, length, ctx) {
|
|
|
2061
2089
|
if (d === -1) d = Math.pow(r.damping, 60 * t);
|
|
2062
2090
|
const m = r.massInverse * t;
|
|
2063
2091
|
const e = r.strength;
|
|
2064
|
-
const
|
|
2065
|
-
const
|
|
2092
|
+
const ax = r.wind * env.wind[0] + r.gravity * env.gravity[0];
|
|
2093
|
+
const ay = r.wind * env.wind[1] + r.gravity * env.gravity[1];
|
|
2066
2094
|
const h = l / f;
|
|
2067
2095
|
const rs = s.rotateOffset;
|
|
2068
2096
|
const ss = s.scaleOffset;
|
|
2069
2097
|
for (; ; ) {
|
|
2070
2098
|
a -= t;
|
|
2071
2099
|
if (scaleX) {
|
|
2072
|
-
s.scaleVelocity += (
|
|
2100
|
+
s.scaleVelocity += (ax * c - ay * sn - s.scaleOffset * e) * m;
|
|
2073
2101
|
s.scaleOffset += s.scaleVelocity * t;
|
|
2074
2102
|
s.scaleVelocity *= d;
|
|
2075
2103
|
}
|
|
2076
2104
|
if (rotateOrShearX) {
|
|
2077
|
-
s.rotateVelocity -= ((
|
|
2105
|
+
s.rotateVelocity -= ((ax * sn + ay * c) * h + s.rotateOffset * e) * m;
|
|
2078
2106
|
s.rotateOffset += s.rotateVelocity * t;
|
|
2079
2107
|
s.rotateVelocity *= d;
|
|
2080
2108
|
if (a < t) break;
|
|
@@ -4938,6 +4966,38 @@ function checkedClock(clock, who) {
|
|
|
4938
4966
|
return moved;
|
|
4939
4967
|
};
|
|
4940
4968
|
}
|
|
4969
|
+
function setEnvironmentOn(ctx, env, who) {
|
|
4970
|
+
if (typeof env !== "object" || env === null || Array.isArray(env)) throw new CoreInputError(`${who} setEnvironment was given ${env === null ? "null" : Array.isArray(env) ? "an array" : typeof env}, not an object { wind?: [x, y], gravity?: [x, y] }`);
|
|
4971
|
+
const given = env;
|
|
4972
|
+
for (const key of Object.keys(given)) {
|
|
4973
|
+
if (key !== "wind" && key !== "gravity") throw new CoreInputError(`${who} setEnvironment names "${key}", which is not one of [wind, gravity]`);
|
|
4974
|
+
}
|
|
4975
|
+
const pair = (key) => {
|
|
4976
|
+
const v = given[key];
|
|
4977
|
+
if (v === void 0) return void 0;
|
|
4978
|
+
if (!Array.isArray(v) || v.length !== 2 || v.some((n) => typeof n !== "number" || !Number.isFinite(n))) throw new CoreInputError(`${who} setEnvironment's ${key} is ${JSON.stringify(v)}, not a pair of finite numbers [x, y] (x to the right, y down)`);
|
|
4979
|
+
return Object.freeze([v[0], v[1]]);
|
|
4980
|
+
};
|
|
4981
|
+
const wind = pair("wind");
|
|
4982
|
+
const gravity = pair("gravity");
|
|
4983
|
+
const standing = ctx.env ?? DEFAULT_PHYSICS_ENVIRONMENT;
|
|
4984
|
+
ctx.env = Object.freeze({ wind: wind ?? standing.wind, gravity: gravity ?? standing.gravity });
|
|
4985
|
+
}
|
|
4986
|
+
function environmentOf(ctx) {
|
|
4987
|
+
return ctx.env ?? DEFAULT_PHYSICS_ENVIRONMENT;
|
|
4988
|
+
}
|
|
4989
|
+
function translateOn(ctx, x, y, who) {
|
|
4990
|
+
for (const [k, v] of [["x", x], ["y", y]]) {
|
|
4991
|
+
if (typeof v !== "number" || !Number.isFinite(v)) throw new CoreInputError(`${who} physicsTranslate's ${k} is ${String(v)}, not a finite distance in the skeleton's world`);
|
|
4992
|
+
}
|
|
4993
|
+
translatePhysics(ctx, x, y);
|
|
4994
|
+
}
|
|
4995
|
+
function rotateOn(ctx, x, y, degrees, who) {
|
|
4996
|
+
for (const [k, v] of [["x", x], ["y", y], ["degrees", degrees]]) {
|
|
4997
|
+
if (typeof v !== "number" || !Number.isFinite(v)) throw new CoreInputError(`${who} physicsRotate's ${k} is ${String(v)}, not a finite number`);
|
|
4998
|
+
}
|
|
4999
|
+
rotatePhysics(ctx, x, y, degrees);
|
|
5000
|
+
}
|
|
4941
5001
|
function loopedTime(trackTime, duration) {
|
|
4942
5002
|
return duration === 0 ? 0 : trackTime % duration;
|
|
4943
5003
|
}
|
|
@@ -5065,6 +5125,25 @@ class Track {
|
|
|
5065
5125
|
get duration() {
|
|
5066
5126
|
return this.track.duration;
|
|
5067
5127
|
}
|
|
5128
|
+
get environment() {
|
|
5129
|
+
return environmentOf(this.track.ctx);
|
|
5130
|
+
}
|
|
5131
|
+
setEnvironment(environment) {
|
|
5132
|
+
this.live("setEnvironment");
|
|
5133
|
+
setEnvironmentOn(this.track.ctx, environment, "the live track's");
|
|
5134
|
+
}
|
|
5135
|
+
physicsTranslate(x, y) {
|
|
5136
|
+
this.live("physicsTranslate");
|
|
5137
|
+
translateOn(this.track.ctx, x, y, "the live track's");
|
|
5138
|
+
}
|
|
5139
|
+
physicsRotate(x, y, degrees) {
|
|
5140
|
+
this.live("physicsRotate");
|
|
5141
|
+
rotateOn(this.track.ctx, x, y, degrees, "the live track's");
|
|
5142
|
+
}
|
|
5143
|
+
/** A refused track refuses every call that would move it, naming the step it was refused at. */
|
|
5144
|
+
live(call) {
|
|
5145
|
+
if (this.refused !== null) throw new CoreInputError(`the live track was refused at step ${this.refused}, after its time and physics had moved; open the track again (${call} not made)`);
|
|
5146
|
+
}
|
|
5068
5147
|
step(dt) {
|
|
5069
5148
|
const n = this.steps + 1;
|
|
5070
5149
|
if (this.refused !== null) throw new CoreInputError(`the live track was refused at step ${this.refused}, after its time and physics had moved; open the track again`);
|
|
@@ -5988,6 +6067,21 @@ class Tracks {
|
|
|
5988
6067
|
get trackTime() {
|
|
5989
6068
|
return this.time;
|
|
5990
6069
|
}
|
|
6070
|
+
get environment() {
|
|
6071
|
+
return environmentOf(this.path.ctx);
|
|
6072
|
+
}
|
|
6073
|
+
setEnvironment(environment) {
|
|
6074
|
+
this.live("setEnvironment");
|
|
6075
|
+
setEnvironmentOn(this.path.ctx, environment, "the live tracks'");
|
|
6076
|
+
}
|
|
6077
|
+
physicsTranslate(x, y) {
|
|
6078
|
+
this.live("physicsTranslate");
|
|
6079
|
+
translateOn(this.path.ctx, x, y, "the live tracks'");
|
|
6080
|
+
}
|
|
6081
|
+
physicsRotate(x, y, degrees) {
|
|
6082
|
+
this.live("physicsRotate");
|
|
6083
|
+
rotateOn(this.path.ctx, x, y, degrees, "the live tracks'");
|
|
6084
|
+
}
|
|
5991
6085
|
live(call) {
|
|
5992
6086
|
if (this.refused !== null) throw new CoreInputError(`the live tracks were refused at ${this.refused}, after their entries and physics had moved; open them again (${call} not made)`);
|
|
5993
6087
|
}
|
|
@@ -8448,6 +8542,7 @@ function frameOf(source, pose, color = [1, 1, 1, 1], draws = () => true) {
|
|
|
8448
8542
|
}
|
|
8449
8543
|
return { order };
|
|
8450
8544
|
}
|
|
8545
|
+
const DEFAULT_ENVIRONMENT = Object.freeze({ wind: Object.freeze([1, 0]), gravity: Object.freeze([0, 1]) });
|
|
8451
8546
|
function predicateOf(names) {
|
|
8452
8547
|
const allowed = new Set(names);
|
|
8453
8548
|
return (slot) => allowed.has(slot);
|
|
@@ -8474,6 +8569,8 @@ class RigPlayer {
|
|
|
8474
8569
|
read;
|
|
8475
8570
|
skinName;
|
|
8476
8571
|
seekStep;
|
|
8572
|
+
/** The environment the page set, or null for the runtime's start values (`environment`). */
|
|
8573
|
+
env = null;
|
|
8477
8574
|
/**
|
|
8478
8575
|
* The single live track (`openTrack`): what a plain `play` on track 0 runs
|
|
8479
8576
|
* on, as every 1.x player did — one animation, every construct the core
|
|
@@ -8601,6 +8698,7 @@ class RigPlayer {
|
|
|
8601
8698
|
this.loop = loop;
|
|
8602
8699
|
this.track = openTrack(this.view, name, { loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
|
|
8603
8700
|
this.pose = this.track.first;
|
|
8701
|
+
this.applyEnvironment(this.track);
|
|
8604
8702
|
return;
|
|
8605
8703
|
}
|
|
8606
8704
|
const tracks = this.compose();
|
|
@@ -8700,9 +8798,58 @@ class RigPlayer {
|
|
|
8700
8798
|
this.tracks = tracks;
|
|
8701
8799
|
this.track = null;
|
|
8702
8800
|
this.pose = tracks.start();
|
|
8801
|
+
this.applyEnvironment(tracks);
|
|
8703
8802
|
if (time > 0) this.stepTo(time);
|
|
8704
8803
|
return tracks;
|
|
8705
8804
|
}
|
|
8805
|
+
/**
|
|
8806
|
+
* The world the rig's physics answers (#121): `wind` and `gravity`, as
|
|
8807
|
+
* spine-core's skeleton-level `windX/windY` and `gravityX/gravityY` — x to
|
|
8808
|
+
* the right and **y down**, the runtime's axes — to which each physics
|
|
8809
|
+
* constraint's authored `wind` / `gravity` is the response. The runtime's
|
|
8810
|
+
* start values are `{ wind: [1, 0], gravity: [0, 1] }`; a player never given
|
|
8811
|
+
* one poses exactly as it did without this method. A half left out keeps its
|
|
8812
|
+
* standing value, `null` returns both to the start values, and the call
|
|
8813
|
+
* applies from the next step on, at any time, with no ramp of the player's
|
|
8814
|
+
* own — the springs are the smoothing. It is the player's, not a track's:
|
|
8815
|
+
* every track and layer answers the same world, and a `seek` or a replay
|
|
8816
|
+
* poses under the environment as it stands (no history is kept). Returns the
|
|
8817
|
+
* environment now standing.
|
|
8818
|
+
*/
|
|
8819
|
+
environment(environment) {
|
|
8820
|
+
if (environment === null) {
|
|
8821
|
+
this.env = null;
|
|
8822
|
+
this.stepper()?.setEnvironment(DEFAULT_ENVIRONMENT);
|
|
8823
|
+
} else if (environment !== void 0) {
|
|
8824
|
+
const live = this.stepper();
|
|
8825
|
+
if (live) {
|
|
8826
|
+
live.setEnvironment(environment);
|
|
8827
|
+
this.env = live.environment;
|
|
8828
|
+
} else {
|
|
8829
|
+
const base = this.env ?? DEFAULT_ENVIRONMENT;
|
|
8830
|
+
this.env = { wind: environment.wind ?? base.wind, gravity: environment.gravity ?? base.gravity };
|
|
8831
|
+
}
|
|
8832
|
+
}
|
|
8833
|
+
return this.stepper()?.environment ?? this.env ?? DEFAULT_ENVIRONMENT;
|
|
8834
|
+
}
|
|
8835
|
+
/**
|
|
8836
|
+
* The page moved or turned the whole rig since the last step (#121), in the
|
|
8837
|
+
* skeleton's world — units, y up, degrees counter-clockwise — so physics
|
|
8838
|
+
* sees it the way spine-core's `Skeleton.physicsTranslate` /
|
|
8839
|
+
* `physicsRotate(0, 0, Δ)` make it: a rig dragged across the page trails
|
|
8840
|
+
* its cloth. The root element's layout stays the caller's; this tells the
|
|
8841
|
+
* physics what the caller did to it (a page-pixel delta divided by the
|
|
8842
|
+
* root's scale, its y negated). Applies to the next step; before `play`
|
|
8843
|
+
* there is nothing to move.
|
|
8844
|
+
*/
|
|
8845
|
+
moved(motion) {
|
|
8846
|
+
const live = this.stepper();
|
|
8847
|
+
if (!live) return;
|
|
8848
|
+
const x = motion.x ?? 0;
|
|
8849
|
+
const y = motion.y ?? 0;
|
|
8850
|
+
if (x !== 0 || y !== 0) live.physicsTranslate(x, y);
|
|
8851
|
+
if (motion.rotation) live.physicsRotate(0, 0, motion.rotation);
|
|
8852
|
+
}
|
|
8706
8853
|
/** Stops stepping; `frame` keeps drawing the pose it holds. */
|
|
8707
8854
|
pause() {
|
|
8708
8855
|
this.paused = true;
|
|
@@ -8735,10 +8882,20 @@ class RigPlayer {
|
|
|
8735
8882
|
this.trackCount = 1;
|
|
8736
8883
|
this.tracks = tracks;
|
|
8737
8884
|
this.pose = tracks.start();
|
|
8885
|
+
this.applyEnvironment(tracks);
|
|
8738
8886
|
return;
|
|
8739
8887
|
}
|
|
8740
8888
|
this.track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook, clock: physicsClockStep });
|
|
8741
8889
|
this.pose = this.track.first;
|
|
8890
|
+
this.applyEnvironment(this.track);
|
|
8891
|
+
}
|
|
8892
|
+
/** What is open: the tracks, the single track, or nothing before `play`. */
|
|
8893
|
+
stepper() {
|
|
8894
|
+
return this.tracks ?? this.track;
|
|
8895
|
+
}
|
|
8896
|
+
/** A newly opened track or tracks answer the environment the player stands in; one never set leaves it untouched. */
|
|
8897
|
+
applyEnvironment(live) {
|
|
8898
|
+
if (this.env !== null) live.setEnvironment(this.env);
|
|
8742
8899
|
}
|
|
8743
8900
|
/** Steps whatever is open forward to `time` in `seekStep` increments, the last one shorter. */
|
|
8744
8901
|
stepTo(time) {
|