rig-play 1.1.0 → 1.2.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 +32 -7
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +119 -9
- package/dist/index.js.map +1 -1
- package/dist/player.d.ts +56 -0
- package/dist/player.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -263,6 +263,35 @@ box, an inverse clip removes nothing, and bounding boxes, paths and points do
|
|
|
263
263
|
not draw and do not count. Both cost nothing until called and read the same
|
|
264
264
|
numbers in `dom` and `canvas` mode.
|
|
265
265
|
|
|
266
|
+
### Adjusting bones
|
|
267
|
+
|
|
268
|
+
A per-pose write to bone locals — a breath's scale, a look's rotation, a mouth
|
|
269
|
+
driven while the body holds still:
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
const player = new RigPlayer(root, assets, {
|
|
273
|
+
adjust: (bones) => { bones.bone('mouth').scaleY = 1 + openness; },
|
|
274
|
+
});
|
|
275
|
+
player.adjust = null; // or set it later; setting it re-poses the held pose at once
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The function runs once per pose, after the animation's timelines and before the
|
|
279
|
+
world transforms, constraints and physics — `rig-c/core`'s `openTrack({ adjust })`,
|
|
280
|
+
which rig-c holds to the Spine runtime at tolerance 0. **A write lasts one
|
|
281
|
+
pose.** The core poses every step from the setup pose, so a value you want held
|
|
282
|
+
is written again each pose and nothing compounds; a 0.8 caller that multiplied a
|
|
283
|
+
channel on a skeleton it kept between frames writes the value it wants, every
|
|
284
|
+
pose. It runs on every pose the animation is applied to, the first one after
|
|
285
|
+
`play` included, and `seek` and a `mode` change keep it.
|
|
286
|
+
|
|
287
|
+
A paused player and a zero step re-pose nothing, so a change the function would
|
|
288
|
+
make is not seen until the next step — unless you ask: setting `player.adjust`
|
|
289
|
+
re-poses the held pose at the same track time, and `player.repose()` does the
|
|
290
|
+
same for a function whose own state changed (the mouth above), without stepping,
|
|
291
|
+
firing events or drawing. A non-finite value, a bone the document does not have,
|
|
292
|
+
or an error the function throws refuses that step in the core's words, and the
|
|
293
|
+
track is reopened by the next `play` or `seek`.
|
|
294
|
+
|
|
266
295
|
### One load, several players
|
|
267
296
|
|
|
268
297
|
One `loadModel` backs any number of players: the regions are cut once, and
|
|
@@ -569,6 +598,9 @@ name, and unloading is still elements first, bitmaps second.
|
|
|
569
598
|
| tint: skeleton × slot × attachment colour | `player.color` × slot × attachment | `player.color` is `[r, g, b, a]` and takes the place of the skeleton's colour. The product arrives as `tint` on each `PosedSlot`. |
|
|
570
599
|
| dark (two-colour) tint: unsupported | `DomRenderer`: unsupported. `CanvasRenderer`: drawn | Carried as `dark` on a `PosedSlot`. The canvas mode's canvas2d fallback draws alpha only. |
|
|
571
600
|
| `renderer.dispose()` | `player.dispose()` | Then `assets.dispose()`, as before. `DomRenderer.dispose()` and `CanvasRenderer.dispose()` exist for a renderer you constructed yourself. |
|
|
601
|
+
| `skeleton.findBone('head').worldX`, `.worldY`, `.a` … `.d` | `player.bone('head')` | `{ x, y, a, b, c, d, rotationX, rotationY, scaleX, scaleY, active }`, the core's world transform for the pose the player holds (the setup pose before `play`). An unknown name is refused by name. Since 1.1.0. |
|
|
602
|
+
| `bone.scaleY = …` on a `Skeleton` between `state.apply` and `updateWorldTransform` | `RigPlayerOptions.adjust`, `player.adjust`, `player.repose()` | A function called once per pose with the pose's bone locals (`bones.bone('mouth').scaleY = v`). A write lasts one pose: the core poses every step from the setup pose, so write the value you want, every pose — nothing compounds. See *Adjusting bones*. Since 1.2.0. |
|
|
603
|
+
| `skeleton.getBounds(offset, size)` | `player.bounds()` | `{ x, y, width, height }` or null, world units, Y up: the box of every drawn region's corners and mesh's vertices, a slot under a clip counted inside the clip's own box. `ModelAssets.stage` remains the declared framing, not this box. Since 1.1.0. |
|
|
572
604
|
|
|
573
605
|
### No counterpart in 1.0.0
|
|
574
606
|
|
|
@@ -586,13 +618,6 @@ export, for which 1.0.0 declares nothing:
|
|
|
586
618
|
load, several players.
|
|
587
619
|
- **More than one track, mixing between animations, or queueing one after another.**
|
|
588
620
|
`RigPlayer` declares one track; `play` reopens it at the animation's first pose.
|
|
589
|
-
- **Reading or writing a bone.** No bone is exposed: nothing sets a bone's local values
|
|
590
|
-
between the step and the draw, and nothing returns a bone's world transform. What is
|
|
591
|
-
readable is the posed slots: `player.posedFrame()` carries each drawn slot's world
|
|
592
|
-
`corners` or `vertices`.
|
|
593
|
-
- **A bounds query for the current pose.** None is declared. `ModelAssets.stage` is the
|
|
594
|
-
stage the document states (or null), which is the rig's declared framing and not the
|
|
595
|
-
box of the pose being drawn.
|
|
596
621
|
- **Changing the skin after construction.** `skin` is a `RigPlayerOptions` field and
|
|
597
622
|
`RigPlayer` declares no way to change it later; a second player over the same assets
|
|
598
623
|
is the declared route.
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ export { type CanvasBackend, CanvasRenderer, type CanvasViewport } from './Canva
|
|
|
2
2
|
export { DomRenderer, type MeshBackend } from './DomRenderer.js';
|
|
3
3
|
export { type PageLayout, type PageRegion, type RegionImage, revokeRegions, unpackRegions } from './DomTexture.js';
|
|
4
4
|
export { type LoadModelOptions, loadModel, type ModelAssets, type ModelDocument } from './loadModel.js';
|
|
5
|
-
export { type BoneWorld, type PoseBounds, type RenderMode, type RigEvent, RigPlayer, type RigPlayerOptions } from './player.js';
|
|
5
|
+
export { type AdjustableBones, type BoneAdjust, type BoneLocals, type BoneWorld, type PoseBounds, type RenderMode, type RigEvent, RigPlayer, type RigPlayerOptions } 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,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnH,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACxG,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,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,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACnH,OAAO,EAAE,KAAK,gBAAgB,EAAE,SAAS,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACxG,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACxL,YAAY,EACV,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,EACT,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -3819,6 +3819,81 @@ function posedBones(doc, timelines, t, plant = {}) {
|
|
|
3819
3819
|
return bone2;
|
|
3820
3820
|
});
|
|
3821
3821
|
}
|
|
3822
|
+
const LOCAL_CHANNELS = { rotation: 0, x: 0, y: 0, scaleX: 1, scaleY: 1, shearX: 0, shearY: 0 };
|
|
3823
|
+
class StepLocals {
|
|
3824
|
+
constructor(bones, index, setup) {
|
|
3825
|
+
this.bones = bones;
|
|
3826
|
+
this.index = index;
|
|
3827
|
+
this.setup = setup;
|
|
3828
|
+
}
|
|
3829
|
+
read(channel2) {
|
|
3830
|
+
return this.bones[this.index][channel2] ?? LOCAL_CHANNELS[channel2];
|
|
3831
|
+
}
|
|
3832
|
+
write(channel2, value) {
|
|
3833
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new CoreInputError(`the adjustment wrote bone "${this.setup.name}" ${channel2} = ${String(value)}, not a finite number`);
|
|
3834
|
+
if (this.bones[this.index] === this.setup) this.bones[this.index] = { ...this.setup };
|
|
3835
|
+
this.bones[this.index][channel2] = value;
|
|
3836
|
+
}
|
|
3837
|
+
get rotation() {
|
|
3838
|
+
return this.read("rotation");
|
|
3839
|
+
}
|
|
3840
|
+
set rotation(v) {
|
|
3841
|
+
this.write("rotation", v);
|
|
3842
|
+
}
|
|
3843
|
+
get x() {
|
|
3844
|
+
return this.read("x");
|
|
3845
|
+
}
|
|
3846
|
+
set x(v) {
|
|
3847
|
+
this.write("x", v);
|
|
3848
|
+
}
|
|
3849
|
+
get y() {
|
|
3850
|
+
return this.read("y");
|
|
3851
|
+
}
|
|
3852
|
+
set y(v) {
|
|
3853
|
+
this.write("y", v);
|
|
3854
|
+
}
|
|
3855
|
+
get scaleX() {
|
|
3856
|
+
return this.read("scaleX");
|
|
3857
|
+
}
|
|
3858
|
+
set scaleX(v) {
|
|
3859
|
+
this.write("scaleX", v);
|
|
3860
|
+
}
|
|
3861
|
+
get scaleY() {
|
|
3862
|
+
return this.read("scaleY");
|
|
3863
|
+
}
|
|
3864
|
+
set scaleY(v) {
|
|
3865
|
+
this.write("scaleY", v);
|
|
3866
|
+
}
|
|
3867
|
+
get shearX() {
|
|
3868
|
+
return this.read("shearX");
|
|
3869
|
+
}
|
|
3870
|
+
set shearX(v) {
|
|
3871
|
+
this.write("shearX", v);
|
|
3872
|
+
}
|
|
3873
|
+
get shearY() {
|
|
3874
|
+
return this.read("shearY");
|
|
3875
|
+
}
|
|
3876
|
+
set shearY(v) {
|
|
3877
|
+
this.write("shearY", v);
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
function adjustLocals(doc, bones, adjust) {
|
|
3881
|
+
const views = /* @__PURE__ */ new Map();
|
|
3882
|
+
const names = doc.bones.map((b) => b.name);
|
|
3883
|
+
adjust({
|
|
3884
|
+
names,
|
|
3885
|
+
bone(name) {
|
|
3886
|
+
let view = views.get(name);
|
|
3887
|
+
if (view === void 0) {
|
|
3888
|
+
const index = names.indexOf(name);
|
|
3889
|
+
if (index < 0) throw new CoreInputError(`the adjustment asked for bone ${JSON.stringify(name)}, which is not one of this document's bones`);
|
|
3890
|
+
view = new StepLocals(bones, index, doc.bones[index]);
|
|
3891
|
+
views.set(name, view);
|
|
3892
|
+
}
|
|
3893
|
+
return view;
|
|
3894
|
+
}
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3822
3897
|
const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
3823
3898
|
function posedSlots(doc, timelines, t, plant = {}, sliders = [], placeholders) {
|
|
3824
3899
|
const resolve = plant.shown ?? shownAttachment;
|
|
@@ -3895,6 +3970,7 @@ function stepRows(posed) {
|
|
|
3895
3970
|
function posedBoneStep(doc, timelines, t, plant, constraints, sliders, step) {
|
|
3896
3971
|
const active = activeBones(doc);
|
|
3897
3972
|
const bones = posedBones(doc, timelines, t, plant);
|
|
3973
|
+
if (step?.adjust !== void 0) adjustLocals(doc, bones, step.adjust);
|
|
3898
3974
|
let world = (plant.evaluate ?? worldTransforms)(bones, active);
|
|
3899
3975
|
if (constraints !== void 0 && step !== void 0) {
|
|
3900
3976
|
const records = stepPhysicsRecords(pathDeformed(doc, posedRecords(constraintRecords(doc), constraints, t), timelines, t, plant), constraints.physicsKeyed ?? [], t, active, step.ctx, step.before);
|
|
@@ -4819,16 +4895,16 @@ function animationOf(doc, animation) {
|
|
|
4819
4895
|
if (anim === void 0) throw new CoreInputError(`animation "${animation}" is not one of this document's [${doc.animations.map((a) => a.name).join(", ")}]`);
|
|
4820
4896
|
return anim;
|
|
4821
4897
|
}
|
|
4822
|
-
function openWalk(doc, anim, plant, reset, mode, shape) {
|
|
4898
|
+
function openWalk(doc, anim, plant, reset, mode, shape, adjust) {
|
|
4823
4899
|
const why = constraintsAbsentWhy(doc) ?? pathAnimationsWhy(doc) ?? steppedPreviousPassWhy(doc);
|
|
4824
4900
|
if (why !== null) throw new CoreInputError(`the raw walk leaves the bones out: ${why}`);
|
|
4825
4901
|
const ctx = freshStepContext(plant.physicsStep);
|
|
4826
4902
|
if (mode.loop && mode.wrapResets !== false) ctx.loop = true;
|
|
4827
|
-
const track = { doc, anim, duration: anim.timelines.duration, mode, plant, raw: { ...plant, ...roundPlant(mode) }, resolve: plant.shown ?? shownAttachment, shape, ctx, trackTime: 0, last: -1 };
|
|
4903
|
+
const track = { doc, anim, duration: anim.timelines.duration, mode, plant, raw: { ...plant, ...roundPlant(mode) }, resolve: plant.shown ?? shownAttachment, shape, ctx, adjust, trackTime: 0, last: -1 };
|
|
4828
4904
|
return { track, first: stepWalk(track, 0, true) };
|
|
4829
4905
|
}
|
|
4830
4906
|
function stepWalk(track, dt, first = false) {
|
|
4831
|
-
const { doc, anim, duration, mode, plant, raw, resolve, shape, ctx } = track;
|
|
4907
|
+
const { doc, anim, duration, mode, plant, raw, resolve, shape, ctx, adjust } = track;
|
|
4832
4908
|
track.trackTime += dt;
|
|
4833
4909
|
const trackTime = track.trackTime;
|
|
4834
4910
|
const last = track.last;
|
|
@@ -4836,7 +4912,7 @@ function stepWalk(track, dt, first = false) {
|
|
|
4836
4912
|
const before = ctx.time;
|
|
4837
4913
|
ctx.time += mode.clock === void 0 ? dt : mode.clock(dt, t - (first ? 0 : Math.max(last, 0)));
|
|
4838
4914
|
const sliders = [];
|
|
4839
|
-
const world = shape.rows ? posedBoneWorld(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before }).world : posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before });
|
|
4915
|
+
const world = shape.rows ? posedBoneWorld(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust }).world : posedBoneWorldAlone(doc, anim.timelines, t, raw, anim.constraints, sliders, { ctx, before, adjust });
|
|
4840
4916
|
if (first) ctx.phase = "update";
|
|
4841
4917
|
const placeholders = /* @__PURE__ */ new Map();
|
|
4842
4918
|
const slots = posedSlots(doc, anim.timelines, t, raw, sliders, placeholders);
|
|
@@ -4856,8 +4932,8 @@ function stepWalk(track, dt, first = false) {
|
|
|
4856
4932
|
track.last = t;
|
|
4857
4933
|
return pose;
|
|
4858
4934
|
}
|
|
4859
|
-
function openRawTrack(doc, animation, plant, reset, mode) {
|
|
4860
|
-
return openWalk(doc, animationOf(doc, animation), plant, reset, mode, RAW_SHAPE);
|
|
4935
|
+
function openRawTrack(doc, animation, plant, reset, mode, adjust) {
|
|
4936
|
+
return openWalk(doc, animationOf(doc, animation), plant, reset, mode, RAW_SHAPE, adjust);
|
|
4861
4937
|
}
|
|
4862
4938
|
function stepRawTrack(track, dt) {
|
|
4863
4939
|
return stepWalk(track, dt);
|
|
@@ -4866,7 +4942,8 @@ const HELD = { loop: false, keep: false };
|
|
|
4866
4942
|
const LOOPED = { loop: true, keep: false, live: true };
|
|
4867
4943
|
function openTrack(doc, animation, options, plant = {}) {
|
|
4868
4944
|
if (typeof options !== "object" || options === null || typeof options.loop !== "boolean") throw new CoreInputError(`the live track's options.loop is ${JSON.stringify(options?.loop)}, not true or false`);
|
|
4869
|
-
|
|
4945
|
+
if (options.adjust !== void 0 && typeof options.adjust !== "function") throw new CoreInputError(`the live track's options.adjust is ${typeof options.adjust}, not a function`);
|
|
4946
|
+
const { track, first } = openRawTrack(doc, animation, plant, options.reset, options.loop ? LOOPED : HELD, options.adjust);
|
|
4870
4947
|
return new Track(track, first);
|
|
4871
4948
|
}
|
|
4872
4949
|
class Track {
|
|
@@ -4896,7 +4973,7 @@ class Track {
|
|
|
4896
4973
|
this.latest = pose.animationTime;
|
|
4897
4974
|
return pose;
|
|
4898
4975
|
} catch (err) {
|
|
4899
|
-
|
|
4976
|
+
this.refused = `${n} (${err instanceof Error ? err.message : String(err)})`;
|
|
4900
4977
|
throw err;
|
|
4901
4978
|
}
|
|
4902
4979
|
}
|
|
@@ -7221,6 +7298,11 @@ class RigPlayer {
|
|
|
7221
7298
|
paused = false;
|
|
7222
7299
|
pose = null;
|
|
7223
7300
|
setup = null;
|
|
7301
|
+
adjustFn = null;
|
|
7302
|
+
/** Handed to every track the player opens; reads the current `adjust` so setting it later needs no reopen. */
|
|
7303
|
+
adjustHook = (locals) => {
|
|
7304
|
+
if (this.adjustFn !== null) this.adjustFn(locals);
|
|
7305
|
+
};
|
|
7224
7306
|
listeners = {
|
|
7225
7307
|
event: /* @__PURE__ */ new Set(),
|
|
7226
7308
|
complete: /* @__PURE__ */ new Set()
|
|
@@ -7234,6 +7316,7 @@ class RigPlayer {
|
|
|
7234
7316
|
this.root = root;
|
|
7235
7317
|
this.assets = assets;
|
|
7236
7318
|
this.meshBackend = options.meshBackend;
|
|
7319
|
+
this.adjustFn = options.adjust ?? null;
|
|
7237
7320
|
this.active = this.create(options.mode ?? "dom");
|
|
7238
7321
|
}
|
|
7239
7322
|
/**
|
|
@@ -7342,6 +7425,33 @@ class RigPlayer {
|
|
|
7342
7425
|
if (this.pose === null) return;
|
|
7343
7426
|
this.renderer.render(this.posedFrame());
|
|
7344
7427
|
}
|
|
7428
|
+
/**
|
|
7429
|
+
* The per-pose adjustment of bone locals (`BoneAdjust`), or null for none.
|
|
7430
|
+
* It is applied by every track the player opens — `play`, the reopen inside
|
|
7431
|
+
* `seek` — and by the pose drawn again after `mode` changes, on every pose
|
|
7432
|
+
* the animation is applied to, the first one included. Setting it re-poses
|
|
7433
|
+
* the pose the player holds at once (`repose`), so a held or paused player
|
|
7434
|
+
* shows the change on its next `draw` without a step.
|
|
7435
|
+
*/
|
|
7436
|
+
get adjust() {
|
|
7437
|
+
return this.adjustFn;
|
|
7438
|
+
}
|
|
7439
|
+
set adjust(fn) {
|
|
7440
|
+
if (fn !== null && typeof fn !== "function") throw new Error(`adjust must be a function or null, got ${typeof fn}`);
|
|
7441
|
+
this.adjustFn = fn;
|
|
7442
|
+
this.repose();
|
|
7443
|
+
}
|
|
7444
|
+
/**
|
|
7445
|
+
* Poses the held pose again at the same track time, through the current
|
|
7446
|
+
* `adjust`, without stepping, firing or drawing. A paused player and a zero
|
|
7447
|
+
* step re-pose nothing, so a caller whose adjustment reads its own state
|
|
7448
|
+
* (a mouth driven while the body is held) calls this when that state
|
|
7449
|
+
* changed, then `draw`. Nothing before `play`.
|
|
7450
|
+
*/
|
|
7451
|
+
repose() {
|
|
7452
|
+
if (this.track === null || this.pose === null) return;
|
|
7453
|
+
this.pose = this.track.step(0);
|
|
7454
|
+
}
|
|
7345
7455
|
/**
|
|
7346
7456
|
* Where `name` is in the pose the player holds — the pose of the last
|
|
7347
7457
|
* `advance`/`frame`/`seek`, or the setup pose before `play`. Read off the
|
|
@@ -7427,7 +7537,7 @@ class RigPlayer {
|
|
|
7427
7537
|
this.pose = null;
|
|
7428
7538
|
}
|
|
7429
7539
|
open() {
|
|
7430
|
-
this.track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation" });
|
|
7540
|
+
this.track = openTrack(this.view, this.animation, { loop: this.loop, reset: "animation", adjust: this.adjustHook });
|
|
7431
7541
|
this.pose = this.track.first;
|
|
7432
7542
|
}
|
|
7433
7543
|
/**
|