@rig-c/three 0.1.0 → 0.1.2
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 +11 -5
- package/dist/index.d.ts +12 -2
- package/dist/index.js +494 -69
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -4,11 +4,15 @@ Draw a rig-c model in a native Three.js scene through `@rig-c/play`. `ThreeRig`
|
|
|
4
4
|
is a flat `Group`: each drawn slot is a `Mesh` with native float32 geometry,
|
|
5
5
|
with one shared shader material per page per instance. It poses no bones itself.
|
|
6
6
|
|
|
7
|
+
## Install
|
|
8
|
+
|
|
7
9
|
```sh
|
|
8
10
|
npm install @rig-c/three three @rig-c/play
|
|
9
11
|
npm install --save-dev @types/three
|
|
10
12
|
```
|
|
11
13
|
|
|
14
|
+
## Minimal usage
|
|
15
|
+
|
|
12
16
|
```ts
|
|
13
17
|
import { loadModel } from '@rig-c/play';
|
|
14
18
|
import { OrthographicCamera, Scene, WebGLRenderer } from 'three';
|
|
@@ -36,15 +40,17 @@ requestAnimationFrame(frame);
|
|
|
36
40
|
```
|
|
37
41
|
|
|
38
42
|
Use the model document and image pages from `rigc build`, unchanged. Tested with
|
|
39
|
-
Three.js **0.186.1** and play **1.
|
|
43
|
+
Three.js **0.186.1** and play **1.20.0** (`@rig-c/play >=1.20.0 <2`). The API is 0.x. See [`packages/three` in the repository](https://github.com/rig-c/adapters/tree/main/packages/three) for source and parity tests.
|
|
40
44
|
|
|
41
45
|
## Live object
|
|
42
46
|
|
|
43
47
|
`play(name, options)`, `seek(seconds)`, `advance(dt)` and `update(dt)` forward to
|
|
44
|
-
an independent `RigPlayer
|
|
48
|
+
an independent `RigPlayer` and consume its `liveFrame()` lease synchronously
|
|
49
|
+
(`liveFrameChecks`, `poser`, `renderFrame(frame)`); deltas are seconds, and the player's `timescale`
|
|
45
50
|
(alias `timeScale`) applies exactly once. The app owns the loop. `timescale = 0`
|
|
46
|
-
holds the drawn frame and uploads no geometry. `
|
|
47
|
-
|
|
51
|
+
holds the drawn frame and uploads no geometry. `draw()` returns a detached
|
|
52
|
+
`PosedFrame`; `drawnFrame()` returns a detached snapshot of the last draw,
|
|
53
|
+
including after direct mutations of `rig.player`; `posedFrame(filter)`, `bone(name)` and `bounds()`
|
|
48
54
|
use play's readers. World bounds are in rig-local Y-up space, before this Group's
|
|
49
55
|
transform. `resourceCounts()` reports owned meshes, geometries, page materials,
|
|
50
56
|
clip textures and retained/shared pages. `destroy()` is idempotent and frees
|
|
@@ -60,7 +66,7 @@ both straight and premultiplied pixels: conflicting `pma` records are refused.
|
|
|
60
66
|
## Native drawing and scene placement
|
|
61
67
|
|
|
62
68
|
`ThreeRenderer(root, assets, webglRenderer)` is available when the app owns the
|
|
63
|
-
player. Call `render(player.posedFrame())
|
|
69
|
+
player. Call `render(player.liveFrame())` (or `render(player.posedFrame())`), then render the scene normally.
|
|
64
70
|
No internal canvas, offscreen bitmap of the rig, or fallback renderer draws it.
|
|
65
71
|
Positions and page UVs come directly from the record; the full atlas page is
|
|
66
72
|
sampled, so packed rotation needs no separate rule. The root stays Y-up and may
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as three from 'three';
|
|
2
2
|
import { Group, WebGLRenderer, Texture, Mesh, BufferGeometry, ShaderMaterial, Camera } from 'three';
|
|
3
|
-
import { ModelAssets, PosedFrame, RigPlayer, SlotFilter, PlayOptions, BoneWorld, PoseBounds, PosedSlot } from '@rig-c/play';
|
|
3
|
+
import { ModelAssets, PosedFrame, LiveFrame, RigPlayer, SlotFilter, PlayOptions, BoneWorld, PoseBounds, PosedSlot } from '@rig-c/play';
|
|
4
4
|
|
|
5
5
|
declare const getLivePageTextureCount: () => number;
|
|
6
6
|
declare const getPageRefCount: (image: HTMLImageElement) => number;
|
|
@@ -31,14 +31,20 @@ declare class ThreeRenderer {
|
|
|
31
31
|
private readonly pool;
|
|
32
32
|
private readonly materials;
|
|
33
33
|
private readonly retained;
|
|
34
|
+
private readonly liveViews;
|
|
35
|
+
private readonly retainedRuntimeFrame;
|
|
36
|
+
private accepted;
|
|
37
|
+
private hasRetained;
|
|
34
38
|
constructor(root: Group, assets: ModelAssets, host: WebGLRenderer);
|
|
39
|
+
get drawnFrame(): PosedFrame | null;
|
|
35
40
|
get geometryUploadCount(): number;
|
|
36
41
|
get isDisposed(): boolean;
|
|
37
42
|
resourceCounts(): ThreeRigResourceCounts;
|
|
38
43
|
getPageTexture(name: string): Texture | undefined;
|
|
39
44
|
private prepare;
|
|
40
45
|
private material;
|
|
41
|
-
render(frame: PosedFrame): void;
|
|
46
|
+
render(frame: PosedFrame | LiveFrame): void;
|
|
47
|
+
private renderOrder;
|
|
42
48
|
dispose(): void;
|
|
43
49
|
}
|
|
44
50
|
/** Read native float32 positions through the application's world and camera mapping. */
|
|
@@ -49,6 +55,8 @@ interface ThreeRigOptions {
|
|
|
49
55
|
slots?: SlotFilter;
|
|
50
56
|
timescale?: number;
|
|
51
57
|
timeScale?: number;
|
|
58
|
+
liveFrameChecks?: boolean;
|
|
59
|
+
poser?: 'auto' | 'oracle';
|
|
52
60
|
}
|
|
53
61
|
/** A flat native Group. Advance from the app's loop using seconds, without an internal clock. */
|
|
54
62
|
declare class ThreeRig extends Group {
|
|
@@ -64,6 +72,8 @@ declare class ThreeRig extends Group {
|
|
|
64
72
|
advance(dt: number): void;
|
|
65
73
|
update(dt: number): void;
|
|
66
74
|
draw(): PosedFrame;
|
|
75
|
+
renderFrame(frame: PosedFrame | LiveFrame): void;
|
|
76
|
+
private drawCurrent;
|
|
67
77
|
drawnFrame(): PosedFrame;
|
|
68
78
|
posedFrame(slots?: SlotFilter | null): PosedFrame;
|
|
69
79
|
bone(name: string): BoneWorld;
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,283 @@
|
|
|
1
|
+
// packages/shared/src/live.ts
|
|
2
|
+
var EMPTY = Object.freeze([]);
|
|
3
|
+
var immutableLengths = /* @__PURE__ */ new WeakSet();
|
|
4
|
+
var immutableSpans = /* @__PURE__ */ new WeakSet();
|
|
5
|
+
var immutableMetadata = /* @__PURE__ */ new WeakSet();
|
|
6
|
+
var IDENTITY_FIELDS = ["slot", "slotIndex", "attachment", "skin", "placeholder", "kind", "region", "page", "pma", "blend", "offset", "count"];
|
|
7
|
+
function isImmutableSpan(value, indexed) {
|
|
8
|
+
if (!value || typeof value !== "object") return false;
|
|
9
|
+
if (immutableSpans.has(value)) return true;
|
|
10
|
+
if (Object.isFrozen(value) && Array.isArray(value)) {
|
|
11
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
12
|
+
for (let i = 0; i < value.length; i++) {
|
|
13
|
+
const d = descriptors[i];
|
|
14
|
+
if (!d || !("value" in d) || typeof d.value !== "number") return false;
|
|
15
|
+
}
|
|
16
|
+
immutableSpans.add(value);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (!indexed || value.length === 0) return false;
|
|
20
|
+
const d0 = Object.getOwnPropertyDescriptor(value, 0);
|
|
21
|
+
return Boolean(d0 && (!d0.writable || "get" in d0));
|
|
22
|
+
}
|
|
23
|
+
function frozenData(value) {
|
|
24
|
+
if (immutableMetadata.has(value)) return true;
|
|
25
|
+
if (!Object.isFrozen(value)) return false;
|
|
26
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
27
|
+
if (!IDENTITY_FIELDS.every((key) => {
|
|
28
|
+
const descriptor = descriptors[key];
|
|
29
|
+
return descriptor && "value" in descriptor && (descriptor.value === null || !["object", "function"].includes(typeof descriptor.value));
|
|
30
|
+
})) return false;
|
|
31
|
+
immutableMetadata.add(value);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
function numbers(value, owner, field, slot) {
|
|
35
|
+
if (value && typeof value === "object" && immutableLengths.has(value)) return;
|
|
36
|
+
if (!value || typeof value !== "object" || !Number.isInteger(value.length) || value.length < 0) {
|
|
37
|
+
throw new Error(`${owner}: live ${slot === void 0 ? field : `slot "${slot}" ${field}`} needs an indexed buffer with nonnegative integer length`);
|
|
38
|
+
}
|
|
39
|
+
const length = Object.getOwnPropertyDescriptor(value, "length");
|
|
40
|
+
if (length && "value" in length && !length.writable && !length.configurable && Object.isFrozen(value)) immutableLengths.add(value);
|
|
41
|
+
}
|
|
42
|
+
var NumberSpan = class {
|
|
43
|
+
constructor(count) {
|
|
44
|
+
this.count = count;
|
|
45
|
+
}
|
|
46
|
+
count;
|
|
47
|
+
source = EMPTY;
|
|
48
|
+
offset = 0;
|
|
49
|
+
};
|
|
50
|
+
function numberSource(value) {
|
|
51
|
+
return value instanceof NumberSpan ? value.source : value;
|
|
52
|
+
}
|
|
53
|
+
function numberOffset(value) {
|
|
54
|
+
return value instanceof NumberSpan ? value.offset : 0;
|
|
55
|
+
}
|
|
56
|
+
function numberCount(value) {
|
|
57
|
+
return value instanceof NumberSpan ? value.count : value ? value.length : -1;
|
|
58
|
+
}
|
|
59
|
+
var LiveDrawViews = class {
|
|
60
|
+
constructor(owner = "PixiRenderer") {
|
|
61
|
+
this.owner = owner;
|
|
62
|
+
}
|
|
63
|
+
owner;
|
|
64
|
+
order = [];
|
|
65
|
+
rows = [];
|
|
66
|
+
validated = /* @__PURE__ */ new WeakSet();
|
|
67
|
+
read(frame) {
|
|
68
|
+
void frame.generation;
|
|
69
|
+
const { drawOrder, vertices, tint, attachments, count } = frame;
|
|
70
|
+
const owner = this.owner;
|
|
71
|
+
numbers(drawOrder, owner, "drawOrder");
|
|
72
|
+
numbers(vertices, owner, "vertices");
|
|
73
|
+
numbers(tint, owner, "tint");
|
|
74
|
+
if (!Number.isInteger(count) || count < 0 || count > drawOrder.length) {
|
|
75
|
+
throw new Error(`${owner}: live frame count=${count} needs an integer in 0..${drawOrder.length}`);
|
|
76
|
+
}
|
|
77
|
+
for (let i = 0; i < count; i++) {
|
|
78
|
+
const index = drawOrder[i];
|
|
79
|
+
const attachment = attachments[index];
|
|
80
|
+
if (!Number.isInteger(index) || !attachment || attachment.kind !== "region" && attachment.kind !== "mesh") {
|
|
81
|
+
throw new Error(`${owner}: live drawOrder[${i}]=${index} needs a region or mesh attachment`);
|
|
82
|
+
}
|
|
83
|
+
const a = attachment;
|
|
84
|
+
if (!this.validated.has(a)) {
|
|
85
|
+
if (typeof a.slot !== "string" || a.slot.length === 0 || typeof a.page !== "string" || a.page.length === 0 || a.kind === "region" && (typeof a.region !== "string" || a.region.length === 0)) {
|
|
86
|
+
throw new Error(`${owner}: live slot "${a.slot}" needs named slot, page and region metadata`);
|
|
87
|
+
}
|
|
88
|
+
if (!Number.isInteger(a.offset) || !Number.isInteger(a.count) || a.offset < 0 || a.count < 0 || !Number.isInteger(a.slotIndex) || a.slotIndex < 0) {
|
|
89
|
+
throw new Error(`${owner}: live slot "${a.slot}" has invalid vertex span ${a.offset}+${a.count} or tint slotIndex=${a.slotIndex}`);
|
|
90
|
+
}
|
|
91
|
+
if (frozenData(a)) this.validated.add(a);
|
|
92
|
+
}
|
|
93
|
+
numbers(a.uvs, owner, "uvs", a.slot);
|
|
94
|
+
numbers(a.triangles, owner, "triangles", a.slot);
|
|
95
|
+
if (a.dark !== null) numbers(a.dark, owner, "dark", a.slot);
|
|
96
|
+
if (a.clip !== null) numbers(a.clip.polygon, owner, "clip.polygon", a.slot);
|
|
97
|
+
if (a.offset + a.count > vertices.length || a.slotIndex * 4 + 4 > tint.length) {
|
|
98
|
+
throw new Error(`${owner}: live slot "${a.slot}" has invalid vertex span ${a.offset}+${a.count} or tint slotIndex=${a.slotIndex}`);
|
|
99
|
+
}
|
|
100
|
+
let row = this.rows[i];
|
|
101
|
+
if (!row || row.slot.kind !== a.kind || row.points.count !== a.count) {
|
|
102
|
+
const points = new NumberSpan(a.count);
|
|
103
|
+
const tint2 = new NumberSpan(4);
|
|
104
|
+
const common = { slot: a.slot, blend: a.blend, tint: tint2, page: a.page ?? "", pma: a.pma, uvs: EMPTY };
|
|
105
|
+
const slot2 = a.kind === "region" ? { ...common, kind: "region", region: a.region ?? "", corners: points } : { ...common, kind: "mesh", vertices: points, triangles: EMPTY };
|
|
106
|
+
row = { slot: slot2, points, tint: tint2 };
|
|
107
|
+
this.rows[i] = row;
|
|
108
|
+
}
|
|
109
|
+
row.points.source = vertices;
|
|
110
|
+
row.points.offset = a.offset;
|
|
111
|
+
row.tint.source = tint;
|
|
112
|
+
row.tint.offset = a.slotIndex * 4;
|
|
113
|
+
const slot = row.slot;
|
|
114
|
+
slot.slot = a.slot;
|
|
115
|
+
slot.blend = a.blend;
|
|
116
|
+
slot.page = a.page ?? "";
|
|
117
|
+
slot.pma = a.pma;
|
|
118
|
+
slot.uvs = a.uvs;
|
|
119
|
+
slot.dark = a.dark ?? void 0;
|
|
120
|
+
slot.clip = a.clip ?? void 0;
|
|
121
|
+
if (slot.kind === "region") slot.region = a.region ?? "";
|
|
122
|
+
else slot.triangles = a.triangles;
|
|
123
|
+
this.order[i] = slot;
|
|
124
|
+
}
|
|
125
|
+
this.order.length = count;
|
|
126
|
+
return this.order;
|
|
127
|
+
}
|
|
128
|
+
release() {
|
|
129
|
+
for (const row of this.rows) {
|
|
130
|
+
row.points.source = EMPTY;
|
|
131
|
+
row.tint.source = EMPTY;
|
|
132
|
+
row.slot.uvs = EMPTY;
|
|
133
|
+
row.slot.dark = void 0;
|
|
134
|
+
row.slot.clip = void 0;
|
|
135
|
+
if (row.slot.kind === "mesh") row.slot.triangles = EMPTY;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
function identity(a) {
|
|
140
|
+
return JSON.stringify([a.slotIndex, a.slot, a.skin, a.placeholder, a.attachment, a.kind, a.page, a.region]);
|
|
141
|
+
}
|
|
142
|
+
function templateFromAttachment(a) {
|
|
143
|
+
const common = {
|
|
144
|
+
slot: a.slot,
|
|
145
|
+
z: a.slotIndex,
|
|
146
|
+
blend: a.blend,
|
|
147
|
+
tint: [1, 1, 1, 1]
|
|
148
|
+
};
|
|
149
|
+
return a.kind === "region" ? {
|
|
150
|
+
...common,
|
|
151
|
+
kind: "region",
|
|
152
|
+
region: a.region ?? "",
|
|
153
|
+
corners: [],
|
|
154
|
+
page: a.page ?? "",
|
|
155
|
+
pma: a.pma,
|
|
156
|
+
uvs: Array.from(a.uvs)
|
|
157
|
+
} : {
|
|
158
|
+
...common,
|
|
159
|
+
kind: "mesh",
|
|
160
|
+
page: a.page ?? "",
|
|
161
|
+
pma: a.pma,
|
|
162
|
+
vertices: [],
|
|
163
|
+
uvs: Array.from(a.uvs),
|
|
164
|
+
triangles: Array.from(a.triangles),
|
|
165
|
+
sequence: 0
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function copyNumbers(target, source) {
|
|
169
|
+
if (ArrayBuffer.isView(source) && source instanceof Float64Array) {
|
|
170
|
+
target.set(source);
|
|
171
|
+
} else {
|
|
172
|
+
for (let i = 0; i < target.length; i++) target[i] = source[i];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function savedNumbers(source, offset, count) {
|
|
176
|
+
const result = new Array(count);
|
|
177
|
+
for (let i = 0; i < count; i++) result[i] = source[offset + i];
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
var RetainedRuntimeFrame = class {
|
|
181
|
+
constructor(owner = "PixiRig") {
|
|
182
|
+
this.owner = owner;
|
|
183
|
+
}
|
|
184
|
+
owner;
|
|
185
|
+
templates = /* @__PURE__ */ new WeakMap();
|
|
186
|
+
immutable = /* @__PURE__ */ new WeakSet();
|
|
187
|
+
checkedTemplates = /* @__PURE__ */ new Map();
|
|
188
|
+
rows = [];
|
|
189
|
+
vertices = new Float64Array(0);
|
|
190
|
+
tint = new Float64Array(0);
|
|
191
|
+
count = 0;
|
|
192
|
+
generation = 0;
|
|
193
|
+
retained = null;
|
|
194
|
+
capture(frame, seed) {
|
|
195
|
+
let snapshot = null;
|
|
196
|
+
const { count, attachments, drawOrder, vertices, tint } = frame;
|
|
197
|
+
if (this.vertices.length !== vertices.length) this.vertices = new Float64Array(vertices.length);
|
|
198
|
+
if (this.tint.length !== tint.length) this.tint = new Float64Array(tint.length);
|
|
199
|
+
copyNumbers(this.vertices, vertices);
|
|
200
|
+
copyNumbers(this.tint, tint);
|
|
201
|
+
for (let i = 0; i < count; i++) {
|
|
202
|
+
const a = attachments[drawOrder[i]];
|
|
203
|
+
let template = this.immutable.has(a) ? this.templates.get(a) : void 0;
|
|
204
|
+
if (!template) {
|
|
205
|
+
const key = identity(a);
|
|
206
|
+
template = this.checkedTemplates.get(key);
|
|
207
|
+
if (!template) {
|
|
208
|
+
if (seed) {
|
|
209
|
+
snapshot ??= seed();
|
|
210
|
+
template = snapshot.order[i];
|
|
211
|
+
if (!template || template.slot !== a.slot || template.kind !== a.kind || template.page !== a.page) {
|
|
212
|
+
throw new Error(`${this.owner}: live slot "${a.slot}" needs a matching snapshot metadata seed at draw index ${i}`);
|
|
213
|
+
}
|
|
214
|
+
} else {
|
|
215
|
+
template = templateFromAttachment(a);
|
|
216
|
+
}
|
|
217
|
+
if (this.checkedTemplates.size >= Math.max(64, frame.attachments.length * 2)) this.checkedTemplates.clear();
|
|
218
|
+
this.checkedTemplates.set(key, template);
|
|
219
|
+
}
|
|
220
|
+
if (frozenData(a)) {
|
|
221
|
+
this.immutable.add(a);
|
|
222
|
+
this.templates.set(a, template);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
let row = this.rows[i];
|
|
226
|
+
if (!row) {
|
|
227
|
+
row = { template, offset: a.offset, count: a.count, tintOffset: a.slotIndex * 4 };
|
|
228
|
+
this.rows[i] = row;
|
|
229
|
+
}
|
|
230
|
+
row.template = template;
|
|
231
|
+
row.offset = a.offset;
|
|
232
|
+
row.count = a.count;
|
|
233
|
+
row.tintOffset = a.slotIndex * 4;
|
|
234
|
+
if (a.dark === null) row.dark = void 0;
|
|
235
|
+
else {
|
|
236
|
+
const dark = row.darkBuffer ??= [];
|
|
237
|
+
dark.length = a.dark.length;
|
|
238
|
+
for (let j = 0; j < dark.length; j++) dark[j] = a.dark[j];
|
|
239
|
+
row.dark = dark;
|
|
240
|
+
}
|
|
241
|
+
if (a.clip === null) row.clip = void 0;
|
|
242
|
+
else {
|
|
243
|
+
const clip = row.clipBuffer ??= { polygon: [], inverse: false, convex: true };
|
|
244
|
+
clip.polygon.length = a.clip.polygon.length;
|
|
245
|
+
for (let j = 0; j < clip.polygon.length; j++) clip.polygon[j] = a.clip.polygon[j];
|
|
246
|
+
clip.inverse = a.clip.inverse;
|
|
247
|
+
clip.convex = a.clip.convex;
|
|
248
|
+
row.clip = clip;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
this.count = frame.count;
|
|
252
|
+
this.generation = frame.generation;
|
|
253
|
+
this.retained = null;
|
|
254
|
+
}
|
|
255
|
+
snapshot() {
|
|
256
|
+
if (this.retained) return this.retained;
|
|
257
|
+
const order = [];
|
|
258
|
+
for (let i = 0; i < this.count; i++) {
|
|
259
|
+
const { template, offset, count, tintOffset, dark, clip } = this.rows[i];
|
|
260
|
+
const common = { ...template, tint: savedNumbers(this.tint, tintOffset, 4) };
|
|
261
|
+
if (dark) common.dark = Array.from(dark);
|
|
262
|
+
else delete common.dark;
|
|
263
|
+
if (clip) common.clip = {
|
|
264
|
+
polygon: Array.from(clip.polygon),
|
|
265
|
+
inverse: clip.inverse,
|
|
266
|
+
convex: clip.convex
|
|
267
|
+
};
|
|
268
|
+
else delete common.clip;
|
|
269
|
+
order.push(template.kind === "region" ? { ...common, kind: "region", region: template.region, corners: savedNumbers(this.vertices, offset, count) } : { ...common, kind: "mesh", vertices: savedNumbers(this.vertices, offset, count), triangles: template.triangles, sequence: template.sequence });
|
|
270
|
+
}
|
|
271
|
+
this.retained = { order };
|
|
272
|
+
Object.defineProperty(this.retained, "generation", { value: this.generation, enumerable: false, configurable: true });
|
|
273
|
+
return this.retained;
|
|
274
|
+
}
|
|
275
|
+
/** Explicit draw() already requested a detached snapshot. */
|
|
276
|
+
retain(frame) {
|
|
277
|
+
this.retained = frame;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
|
|
1
281
|
// packages/three/src/three.ts
|
|
2
282
|
import { RigPlayer } from "@rig-c/play";
|
|
3
283
|
import {
|
|
@@ -39,11 +319,12 @@ function hull(points) {
|
|
|
39
319
|
};
|
|
40
320
|
return [...half(sorted).slice(0, -1), ...half([...sorted].reverse()).slice(0, -1)];
|
|
41
321
|
}
|
|
42
|
-
function clipPolygon(clip, detail, capacity) {
|
|
322
|
+
function clipPolygon(clip, detail, capacity, indexed = false) {
|
|
43
323
|
const fail = (why) => {
|
|
44
324
|
throw new Error(`${detail} has invalid clip: ${why} (expected finite simple nonzero-area polygon, boolean inverse/convex, at most GPU MAX_TEXTURE_SIZE=${capacity} vertices)`);
|
|
45
325
|
};
|
|
46
|
-
|
|
326
|
+
const validPolygon = indexed ? Boolean(clip && clip.polygon && typeof clip.polygon === "object" && Number.isInteger(clip.polygon.length)) : Boolean(clip && Array.isArray(clip.polygon));
|
|
327
|
+
if (!clip || typeof clip.inverse !== "boolean" || typeof clip.convex !== "boolean" || !validPolygon || clip.polygon.length < 6 || clip.polygon.length % 2 !== 0) fail("invalid flags or polygon length");
|
|
47
328
|
if (clip.polygon.length / 2 > capacity) fail(`THREE_CLIP_CAPACITY: found ${clip.polygon.length / 2} vertices`);
|
|
48
329
|
const points = [];
|
|
49
330
|
for (let i = 0; i < clip.polygon.length; i += 2) {
|
|
@@ -80,7 +361,7 @@ var livePages = /* @__PURE__ */ new Set();
|
|
|
80
361
|
var getLivePageTextureCount = () => livePages.size;
|
|
81
362
|
var getPageRefCount = (image) => pages.get(image)?.holders.size ?? 0;
|
|
82
363
|
var getSharedPageTexture = (image) => pages.get(image)?.texture;
|
|
83
|
-
var REGION_INDICES = [0, 1, 2, 2, 3, 0];
|
|
364
|
+
var REGION_INDICES = Object.freeze([0, 1, 2, 2, 3, 0]);
|
|
84
365
|
var VERTEX = `
|
|
85
366
|
out vec2 vUV;
|
|
86
367
|
out vec2 vPosition;
|
|
@@ -137,6 +418,16 @@ var ThreeRenderer = class {
|
|
|
137
418
|
pool = /* @__PURE__ */ new Map();
|
|
138
419
|
materials = /* @__PURE__ */ new Map();
|
|
139
420
|
retained = /* @__PURE__ */ new Set();
|
|
421
|
+
liveViews = new LiveDrawViews("ThreeRenderer");
|
|
422
|
+
retainedRuntimeFrame = new RetainedRuntimeFrame("ThreeRenderer");
|
|
423
|
+
accepted = null;
|
|
424
|
+
hasRetained = false;
|
|
425
|
+
get drawnFrame() {
|
|
426
|
+
if (this.accepted) return this.accepted;
|
|
427
|
+
if (!this.hasRetained) return null;
|
|
428
|
+
this.accepted = this.retainedRuntimeFrame.snapshot();
|
|
429
|
+
return this.accepted;
|
|
430
|
+
}
|
|
140
431
|
get geometryUploadCount() {
|
|
141
432
|
return this.positionUploadCount + this.uvUploadCount + this.triangleUploadCount;
|
|
142
433
|
}
|
|
@@ -150,7 +441,7 @@ var ThreeRenderer = class {
|
|
|
150
441
|
const image = this.assets.pageImages.get(name);
|
|
151
442
|
return image && this.retained.has(image) ? pages.get(image)?.texture : void 0;
|
|
152
443
|
}
|
|
153
|
-
prepare(slot) {
|
|
444
|
+
prepare(slot, key, indexed) {
|
|
154
445
|
const detail = `ThreeRenderer: slot "${slot?.slot ?? "<unnamed>"}" on page "${slot?.page ?? "<unnamed>"}"`;
|
|
155
446
|
const fail = (why) => {
|
|
156
447
|
throw new Error(`${detail}: ${why}`);
|
|
@@ -159,26 +450,58 @@ var ThreeRenderer = class {
|
|
|
159
450
|
if (typeof slot.slot !== "string" || slot.slot.length === 0 || typeof slot.page !== "string" || slot.page.length === 0) fail("expected non-empty slot and page names");
|
|
160
451
|
if (!["Normal", "Additive", "Multiply", "Screen"].includes(slot.blend)) fail(`blend=${slot.blend} (expected Normal, Additive, Multiply, Screen)`);
|
|
161
452
|
if (typeof slot.pma !== "boolean") fail(`pma=${String(slot.pma)} (expected boolean)`);
|
|
162
|
-
if (!Number.isFinite(slot.z)) fail(`z=${String(slot.z)} (expected finite draw-order index)`);
|
|
453
|
+
if (!indexed && !Number.isFinite(slot.z)) fail(`z=${String(slot.z)} (expected finite draw-order index)`);
|
|
163
454
|
const image = this.assets.pageImages.get(slot.page);
|
|
164
455
|
if (!image || !(image.naturalWidth > 0 && image.naturalHeight > 0)) fail("page image absent or not decoded (expected positive naturalWidth/naturalHeight)");
|
|
165
456
|
const valid = (values, count, field) => {
|
|
166
|
-
|
|
167
|
-
|
|
457
|
+
const source = numberSource(values), offset = numberOffset(values), length = numberCount(values);
|
|
458
|
+
if (!indexed && !Array.isArray(values) || length !== count) fail(`${field} length=${Array.isArray(values) || indexed ? length : void 0} (expected ${count})`);
|
|
459
|
+
for (let i = 0; i < count; i++) {
|
|
460
|
+
const value = source[offset + i];
|
|
461
|
+
if (!Number.isFinite(value) || !Number.isFinite(Math.fround(value))) fail(`${field}[${i}]=${String(value)} (expected finite float32)`);
|
|
462
|
+
}
|
|
168
463
|
};
|
|
169
464
|
valid(slot.tint, 4, "tint");
|
|
170
465
|
if (slot.dark !== void 0) valid(slot.dark, 3, "dark");
|
|
171
466
|
const coords = slot.kind === "region" ? slot.corners : slot.vertices;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
valid(slot.
|
|
467
|
+
const coordCount = numberCount(coords);
|
|
468
|
+
if (!indexed && !Array.isArray(coords) || coordCount < 6 || coordCount % 2 !== 0) fail(`positions length=${Array.isArray(coords) || indexed ? coordCount : void 0} (expected even length >=6)`);
|
|
469
|
+
valid(coords, slot.kind === "region" ? 8 : coordCount, slot.kind === "region" ? "corners" : "vertices");
|
|
470
|
+
const prior = this.pool.get(key);
|
|
471
|
+
const priorPositions = prior?.mesh.geometry.getAttribute("position");
|
|
472
|
+
const sameVertexCount = !this.alwaysReuploadGeometry && prior !== void 0 && priorPositions !== void 0 && priorPositions.array.length === coordCount / 2 * 3;
|
|
473
|
+
if (!sameVertexCount || !isImmutableSpan(slot.uvs, indexed) || prior.uvs !== slot.uvs) {
|
|
474
|
+
valid(slot.uvs, coordCount, "uvs");
|
|
475
|
+
}
|
|
175
476
|
const indices = slot.kind === "region" ? REGION_INDICES : slot.triangles;
|
|
176
|
-
if (!
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
477
|
+
if (!sameVertexCount || !isImmutableSpan(indices, indexed) || prior.indices !== indices) {
|
|
478
|
+
if (!indexed && !Array.isArray(indices) || indices.length < 3 || indices.length % 3 !== 0) fail(`triangles length=${indices?.length} (expected multiple of three >=3)`);
|
|
479
|
+
for (let i = 0; i < indices.length; i++) {
|
|
480
|
+
const v = indices[i];
|
|
481
|
+
if (!Number.isInteger(v) || v < 0 || v >= coordCount / 2) fail(`triangles[${i}]=${v} (expected integer 0..${coordCount / 2 - 1})`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (!indexed && slot.kind === "mesh" && !Number.isInteger(slot.sequence)) fail(`sequence=${slot.sequence} (expected integer)`);
|
|
485
|
+
const tintSource = numberSource(slot.tint), tintOffset = numberOffset(slot.tint);
|
|
486
|
+
const light = [
|
|
487
|
+
tintSource[tintOffset + 0],
|
|
488
|
+
tintSource[tintOffset + 1],
|
|
489
|
+
tintSource[tintOffset + 2],
|
|
490
|
+
tintSource[tintOffset + 3]
|
|
491
|
+
];
|
|
492
|
+
const dark = slot.dark ? [slot.dark[0], slot.dark[1], slot.dark[2]] : [0, 0, 0];
|
|
493
|
+
return {
|
|
494
|
+
slot,
|
|
495
|
+
key,
|
|
496
|
+
image,
|
|
497
|
+
positions: coords,
|
|
498
|
+
indices,
|
|
499
|
+
polygon: slot.clip !== void 0 ? clipPolygon(slot.clip, detail, this.host.capabilities.maxTextureSize, indexed) : null,
|
|
500
|
+
light,
|
|
501
|
+
dark,
|
|
502
|
+
inverse: slot.clip?.inverse ?? false,
|
|
503
|
+
blend: slot.blend
|
|
504
|
+
};
|
|
182
505
|
}
|
|
183
506
|
material(item) {
|
|
184
507
|
let material = this.materials.get(item.image);
|
|
@@ -216,71 +539,162 @@ var ThreeRenderer = class {
|
|
|
216
539
|
}
|
|
217
540
|
render(frame) {
|
|
218
541
|
if (this.disposed) throw new Error("ThreeRenderer: render called after dispose (expected active renderer)");
|
|
219
|
-
if (!frame ||
|
|
220
|
-
|
|
542
|
+
if (!frame || typeof frame !== "object") throw new Error("ThreeRenderer: frame.order absent (expected PosedSlot array)");
|
|
543
|
+
if ("order" in frame) {
|
|
544
|
+
if (!Array.isArray(frame.order)) throw new Error("ThreeRenderer: frame.order absent (expected PosedSlot array)");
|
|
545
|
+
this.renderOrder(frame.order, false);
|
|
546
|
+
this.accepted = frame;
|
|
547
|
+
this.hasRetained = false;
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
this.renderOrder(this.liveViews.read(frame), true);
|
|
552
|
+
this.retainedRuntimeFrame.capture(frame);
|
|
553
|
+
this.accepted = null;
|
|
554
|
+
this.hasRetained = true;
|
|
555
|
+
} finally {
|
|
556
|
+
this.liveViews.release();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
renderOrder(order, indexed) {
|
|
560
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
561
|
+
const prepared = order.map((s) => {
|
|
562
|
+
const slotName = typeof s?.slot === "string" && s.slot.length > 0 ? s.slot : "<unnamed>";
|
|
563
|
+
const occurrence = occurrences.get(slotName) ?? 0;
|
|
564
|
+
occurrences.set(slotName, occurrence + 1);
|
|
565
|
+
return this.prepare(s, `${slotName}\0${occurrence}`, indexed);
|
|
566
|
+
});
|
|
221
567
|
const conventions = /* @__PURE__ */ new Map();
|
|
222
568
|
for (const item of prepared) {
|
|
223
569
|
const pma = conventions.get(item.image) ?? pages.get(item.image)?.pma;
|
|
224
570
|
if (pma !== void 0 && pma !== item.slot.pma) throw new Error(`ThreeRenderer: slot "${item.slot.slot}" page "${item.slot.page}" pma=${item.slot.pma} conflicts with retained pma=${pma} (expected one convention per page image)`);
|
|
225
571
|
conventions.set(item.image, item.slot.pma);
|
|
226
572
|
}
|
|
227
|
-
const occurrences = /* @__PURE__ */ new Map();
|
|
228
573
|
const active = [];
|
|
229
574
|
prepared.forEach((item, index) => {
|
|
230
|
-
const
|
|
231
|
-
occurrences.set(item.slot.slot, occurrence + 1);
|
|
232
|
-
const key = `${item.slot.slot}\0${occurrence}`;
|
|
575
|
+
const { key } = item;
|
|
233
576
|
let entry = this.pool.get(key);
|
|
234
577
|
if (!entry) {
|
|
235
578
|
const geometry = new BufferGeometry();
|
|
236
579
|
const mesh2 = new Mesh(geometry, this.material(item));
|
|
237
580
|
mesh2.frustumCulled = false;
|
|
238
581
|
mesh2.name = item.slot.slot;
|
|
239
|
-
entry = {
|
|
582
|
+
entry = {
|
|
583
|
+
mesh: mesh2,
|
|
584
|
+
polygon: null,
|
|
585
|
+
polygonValues: [],
|
|
586
|
+
uvs: null,
|
|
587
|
+
indices: null,
|
|
588
|
+
light: item.light,
|
|
589
|
+
dark: item.dark,
|
|
590
|
+
inverse: item.inverse,
|
|
591
|
+
blend: item.blend,
|
|
592
|
+
needsWideIndex: false
|
|
593
|
+
};
|
|
594
|
+
const current = entry;
|
|
595
|
+
mesh2.onBeforeRender = () => {
|
|
596
|
+
const material = mesh2.material;
|
|
597
|
+
material.uniforms.light.value = current.light;
|
|
598
|
+
material.uniforms.dark.value = current.dark;
|
|
599
|
+
material.uniforms.polygon.value = current.polygon ?? material.uniforms.page.value;
|
|
600
|
+
material.uniforms.points.value = current.polygon === null ? 0 : current.polygonValues.length / 2;
|
|
601
|
+
material.uniforms.inverse.value = current.inverse;
|
|
602
|
+
material.blendEquation = material.blendEquationAlpha = AddEquation;
|
|
603
|
+
material.blendSrc = current.blend === "Multiply" ? DstColorFactor : OneFactor;
|
|
604
|
+
material.blendDst = current.blend === "Additive" ? OneFactor : current.blend === "Screen" ? OneMinusSrcColorFactor : OneMinusSrcAlphaFactor;
|
|
605
|
+
material.blendSrcAlpha = OneFactor;
|
|
606
|
+
material.blendDstAlpha = material.blendDst;
|
|
607
|
+
material.uniformsNeedUpdate = true;
|
|
608
|
+
};
|
|
240
609
|
this.pool.set(key, entry);
|
|
241
610
|
}
|
|
242
611
|
const { mesh } = entry;
|
|
612
|
+
const positionCount = numberCount(item.positions);
|
|
243
613
|
const oldPositions = mesh.geometry.getAttribute("position"), oldUVs = mesh.geometry.getAttribute("uv"), oldIndex = mesh.geometry.getIndex();
|
|
244
|
-
|
|
245
|
-
if (
|
|
614
|
+
let needsWideIndex = entry.needsWideIndex;
|
|
615
|
+
if (this.alwaysReuploadGeometry || entry.indices !== item.indices || !oldIndex) {
|
|
616
|
+
needsWideIndex = false;
|
|
617
|
+
if (positionCount / 2 > 65535) {
|
|
618
|
+
for (let i = 0; i < item.indices.length; i++) if (item.indices[i] >= 65535) {
|
|
619
|
+
needsWideIndex = true;
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
entry.needsWideIndex = needsWideIndex;
|
|
624
|
+
}
|
|
625
|
+
if (oldPositions && oldPositions.array.length !== positionCount / 2 * 3 || oldUVs && oldUVs.array.length !== item.slot.uvs.length || oldIndex && (oldIndex.count !== item.indices.length || needsWideIndex && !(oldIndex.array instanceof Uint32Array))) {
|
|
246
626
|
mesh.geometry.dispose();
|
|
247
627
|
mesh.geometry = new BufferGeometry();
|
|
248
628
|
entry.uvs = entry.indices = null;
|
|
249
629
|
}
|
|
250
|
-
|
|
630
|
+
entry.light = item.light;
|
|
631
|
+
entry.dark = item.dark;
|
|
632
|
+
entry.inverse = item.inverse;
|
|
633
|
+
entry.blend = item.blend;
|
|
634
|
+
if (!indexed) {
|
|
635
|
+
mesh.posedSlot = item.slot;
|
|
636
|
+
} else {
|
|
637
|
+
const prior = mesh.posedSlot;
|
|
638
|
+
if (!prior || prior.slot !== item.slot.slot || prior.page !== item.slot.page || prior.pma !== item.slot.pma || prior.kind !== item.slot.kind || prior.blend !== item.slot.blend) {
|
|
639
|
+
mesh.posedSlot = item.slot.kind === "region" ? { kind: "region", slot: item.slot.slot, z: 0, page: item.slot.page, region: item.slot.region, pma: item.slot.pma, blend: item.slot.blend, corners: [], uvs: [], tint: item.light } : { kind: "mesh", slot: item.slot.slot, z: 0, page: item.slot.page, pma: item.slot.pma, blend: item.slot.blend, vertices: [], uvs: [], triangles: [], sequence: 0, tint: item.light };
|
|
640
|
+
}
|
|
641
|
+
}
|
|
251
642
|
mesh.material = this.material(item);
|
|
252
643
|
mesh.renderOrder = index;
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
644
|
+
const posLength = positionCount / 2 * 3;
|
|
645
|
+
let posAttr = mesh.geometry.getAttribute("position");
|
|
646
|
+
let posChanged = this.alwaysReuploadGeometry || !posAttr || posAttr.array.length !== posLength;
|
|
647
|
+
if (!posAttr || posAttr.array.length !== posLength) {
|
|
648
|
+
posAttr = new BufferAttribute(new Float32Array(posLength), 3).setUsage(DynamicDrawUsage);
|
|
649
|
+
mesh.geometry.setAttribute("position", posAttr);
|
|
650
|
+
}
|
|
651
|
+
const posArray = posAttr.array;
|
|
652
|
+
const posSource = numberSource(item.positions), posOffset = numberOffset(item.positions);
|
|
653
|
+
for (let i = 0, out = 0; i < positionCount; i += 2, out += 3) {
|
|
654
|
+
const x = Math.fround(posSource[posOffset + i]), y = Math.fround(posSource[posOffset + i + 1]);
|
|
655
|
+
if (posArray[out] !== x || posArray[out + 1] !== y || posArray[out + 2] !== 0) {
|
|
656
|
+
posArray[out] = x;
|
|
657
|
+
posArray[out + 1] = y;
|
|
658
|
+
posArray[out + 2] = 0;
|
|
659
|
+
posChanged = true;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (posChanged) {
|
|
663
|
+
posAttr.needsUpdate = true;
|
|
664
|
+
this.positionUploadCount++;
|
|
665
|
+
}
|
|
666
|
+
if (this.alwaysReuploadGeometry || entry.uvs !== item.slot.uvs) {
|
|
667
|
+
const uvs = item.slot.uvs;
|
|
668
|
+
let uvAttr = mesh.geometry.getAttribute("uv");
|
|
669
|
+
let uvChanged = true;
|
|
670
|
+
if (!uvAttr || uvAttr.array.length !== uvs.length) {
|
|
671
|
+
uvAttr = new BufferAttribute(new Float32Array(uvs.length), 2).setUsage(DynamicDrawUsage);
|
|
672
|
+
mesh.geometry.setAttribute("uv", uvAttr);
|
|
259
673
|
}
|
|
260
|
-
const
|
|
261
|
-
for (let i = 0; i <
|
|
262
|
-
|
|
263
|
-
|
|
674
|
+
const uvArray = uvAttr.array;
|
|
675
|
+
for (let i = 0; i < uvs.length; i++) {
|
|
676
|
+
const v = Math.fround(uvs[i]);
|
|
677
|
+
if (uvArray[i] !== v) {
|
|
678
|
+
uvArray[i] = v;
|
|
679
|
+
uvChanged = true;
|
|
680
|
+
}
|
|
264
681
|
}
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
else this.uvUploadCount++;
|
|
682
|
+
if (uvChanged) {
|
|
683
|
+
uvAttr.needsUpdate = true;
|
|
684
|
+
this.uvUploadCount++;
|
|
269
685
|
}
|
|
270
|
-
|
|
271
|
-
const xyz = [];
|
|
272
|
-
for (let i = 0; i < item.positions.length; i += 2) xyz.push(item.positions[i], item.positions[i + 1], 0);
|
|
273
|
-
update("position", xyz, 3, this.alwaysReuploadGeometry);
|
|
274
|
-
if (this.alwaysReuploadGeometry || entry.uvs !== item.slot.uvs) {
|
|
275
|
-
update("uv", item.slot.uvs, 2, true);
|
|
276
|
-
entry.uvs = item.slot.uvs;
|
|
686
|
+
entry.uvs = uvs;
|
|
277
687
|
}
|
|
278
688
|
if (this.alwaysReuploadGeometry || entry.indices !== item.indices) {
|
|
279
689
|
const before = mesh.geometry.getIndex();
|
|
280
690
|
if (before && before.count === item.indices.length) {
|
|
281
691
|
for (let i = 0; i < item.indices.length; i++) before.setX(i, item.indices[i]);
|
|
282
692
|
before.needsUpdate = true;
|
|
283
|
-
} else
|
|
693
|
+
} else {
|
|
694
|
+
const copied = new Array(item.indices.length);
|
|
695
|
+
for (let i = 0; i < item.indices.length; i++) copied[i] = item.indices[i];
|
|
696
|
+
mesh.geometry.setIndex(copied);
|
|
697
|
+
}
|
|
284
698
|
this.triangleUploadCount++;
|
|
285
699
|
entry.indices = item.indices;
|
|
286
700
|
}
|
|
@@ -300,21 +714,6 @@ var ThreeRenderer = class {
|
|
|
300
714
|
entry.polygon = null;
|
|
301
715
|
entry.polygonValues = [];
|
|
302
716
|
}
|
|
303
|
-
const current = entry;
|
|
304
|
-
mesh.onBeforeRender = () => {
|
|
305
|
-
const material = mesh.material, slot = mesh.posedSlot;
|
|
306
|
-
material.uniforms.light.value = slot.tint;
|
|
307
|
-
material.uniforms.dark.value = slot.dark ?? [0, 0, 0];
|
|
308
|
-
material.uniforms.polygon.value = current.polygon ?? material.uniforms.page.value;
|
|
309
|
-
material.uniforms.points.value = current.polygon === null ? 0 : current.polygonValues.length / 2;
|
|
310
|
-
material.uniforms.inverse.value = slot.clip?.inverse ?? false;
|
|
311
|
-
material.blendEquation = material.blendEquationAlpha = AddEquation;
|
|
312
|
-
material.blendSrc = slot.blend === "Multiply" ? DstColorFactor : OneFactor;
|
|
313
|
-
material.blendDst = slot.blend === "Additive" ? OneFactor : slot.blend === "Screen" ? OneMinusSrcColorFactor : OneMinusSrcAlphaFactor;
|
|
314
|
-
material.blendSrcAlpha = OneFactor;
|
|
315
|
-
material.blendDstAlpha = material.blendDst;
|
|
316
|
-
material.uniformsNeedUpdate = true;
|
|
317
|
-
};
|
|
318
717
|
active.push(mesh);
|
|
319
718
|
});
|
|
320
719
|
for (const entry of this.pool.values()) if (entry.mesh.parent === this.root) this.root.remove(entry.mesh);
|
|
@@ -361,7 +760,13 @@ var ThreeRig = class extends Group {
|
|
|
361
760
|
if (!assets?.document || !(assets.pageImages instanceof Map) || !isWebGLHost(options?.renderer)) throw new Error("ThreeRig: expected ModelAssets and options.renderer WebGLRenderer");
|
|
362
761
|
const scale = options.timescale ?? options.timeScale ?? 1;
|
|
363
762
|
if (!Number.isFinite(scale) || scale < 0) throw new Error(`ThreeRig: timescale=${scale} (expected finite number >=0)`);
|
|
364
|
-
this.player = new RigPlayer(document.createElement("div"), assets, {
|
|
763
|
+
this.player = new RigPlayer(document.createElement("div"), assets, {
|
|
764
|
+
mode: assets.regions === "none" ? "canvas" : "dom",
|
|
765
|
+
skin: options.skin,
|
|
766
|
+
slots: options.slots,
|
|
767
|
+
liveFrameChecks: options.liveFrameChecks,
|
|
768
|
+
poser: options.poser
|
|
769
|
+
});
|
|
365
770
|
this.renderer = new ThreeRenderer(this, assets, options.renderer);
|
|
366
771
|
this.timescale = scale;
|
|
367
772
|
try {
|
|
@@ -375,7 +780,7 @@ var ThreeRig = class extends Group {
|
|
|
375
780
|
assets;
|
|
376
781
|
player;
|
|
377
782
|
renderer;
|
|
378
|
-
lastFrame;
|
|
783
|
+
lastFrame = null;
|
|
379
784
|
destroyed = false;
|
|
380
785
|
active() {
|
|
381
786
|
if (this.destroyed) throw new Error("ThreeRig: called after destroy (expected active rig)");
|
|
@@ -383,20 +788,20 @@ var ThreeRig = class extends Group {
|
|
|
383
788
|
play(name, options = {}) {
|
|
384
789
|
this.active();
|
|
385
790
|
this.player.play(name, options);
|
|
386
|
-
this.
|
|
791
|
+
this.drawCurrent();
|
|
387
792
|
}
|
|
388
793
|
seek(time) {
|
|
389
794
|
this.active();
|
|
390
795
|
if (!Number.isFinite(time)) throw new Error(`ThreeRig: time=${time} (expected finite seconds)`);
|
|
391
796
|
this.player.seek(time);
|
|
392
|
-
this.
|
|
797
|
+
this.drawCurrent();
|
|
393
798
|
}
|
|
394
799
|
advance(dt) {
|
|
395
800
|
this.active();
|
|
396
801
|
if (!Number.isFinite(dt) || dt < 0) throw new Error(`ThreeRig: dt=${dt} (expected finite seconds >=0)`);
|
|
397
802
|
if (this.timescale === 0) return;
|
|
398
803
|
this.player.advance(dt);
|
|
399
|
-
this.
|
|
804
|
+
this.drawCurrent();
|
|
400
805
|
}
|
|
401
806
|
update(dt) {
|
|
402
807
|
this.advance(dt);
|
|
@@ -408,9 +813,29 @@ var ThreeRig = class extends Group {
|
|
|
408
813
|
this.lastFrame = frame;
|
|
409
814
|
return frame;
|
|
410
815
|
}
|
|
816
|
+
renderFrame(frame) {
|
|
817
|
+
this.active();
|
|
818
|
+
if ("order" in frame) {
|
|
819
|
+
this.renderer.render(frame);
|
|
820
|
+
this.lastFrame = frame;
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
if (frame.generation !== this.player.generation) {
|
|
824
|
+
throw new Error(`ThreeRig: live frame generation ${frame.generation} needs current player generation ${this.player.generation}`);
|
|
825
|
+
}
|
|
826
|
+
if (this.player.poser !== "runtime") {
|
|
827
|
+
this.renderFrame(this.player.posedFrame());
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
this.renderer.render(frame);
|
|
831
|
+
this.lastFrame = null;
|
|
832
|
+
}
|
|
833
|
+
drawCurrent() {
|
|
834
|
+
this.renderFrame(this.player.poser === "runtime" ? this.player.liveFrame() : this.player.posedFrame());
|
|
835
|
+
}
|
|
411
836
|
drawnFrame() {
|
|
412
837
|
this.active();
|
|
413
|
-
return this.lastFrame;
|
|
838
|
+
return this.lastFrame ??= this.renderer.drawnFrame;
|
|
414
839
|
}
|
|
415
840
|
posedFrame(slots) {
|
|
416
841
|
this.active();
|
package/package.json
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/three",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Play rig-c model documents in a native Three.js scene through @rig-c/play.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/rig-c/adapters.git",
|
|
9
|
+
"url": "git+https://github.com/rig-c/adapters.git",
|
|
10
10
|
"directory": "packages/three"
|
|
11
11
|
},
|
|
12
|
+
"homepage": "https://github.com/rig-c/adapters/tree/main/packages/three#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/rig-c/adapters/issues"
|
|
15
|
+
},
|
|
12
16
|
"exports": {
|
|
13
17
|
".": {
|
|
14
18
|
"types": "./dist/index.d.ts",
|
|
@@ -24,7 +28,7 @@
|
|
|
24
28
|
"sideEffects": false,
|
|
25
29
|
"peerDependencies": {
|
|
26
30
|
"three": "^0.186.1",
|
|
27
|
-
"@rig-c/play": "^1.
|
|
31
|
+
"@rig-c/play": "^1.20.0"
|
|
28
32
|
},
|
|
29
33
|
"devDependencies": {
|
|
30
34
|
"@rig-c/adapters-shared": "workspace:*",
|