@rig-c/three 0.1.1 → 0.1.3
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 +504 -69
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/rig-c/adapters/main/packages/three/assets/banner.svg" alt="@rig-c/three — Draw a rig-c model in a native Three.js scene through @rig-c/play" width="100%" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# @rig-c/three
|
|
2
6
|
|
|
3
7
|
Draw a rig-c model in a native Three.js scene through `@rig-c/play`. `ThreeRig`
|
|
@@ -40,15 +44,17 @@ requestAnimationFrame(frame);
|
|
|
40
44
|
```
|
|
41
45
|
|
|
42
46
|
Use the model document and image pages from `rigc build`, unchanged. Tested with
|
|
43
|
-
Three.js **0.186.1** and play **1.
|
|
47
|
+
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.
|
|
44
48
|
|
|
45
49
|
## Live object
|
|
46
50
|
|
|
47
51
|
`play(name, options)`, `seek(seconds)`, `advance(dt)` and `update(dt)` forward to
|
|
48
|
-
an independent `RigPlayer
|
|
52
|
+
an independent `RigPlayer` and consume its `liveFrame()` lease synchronously
|
|
53
|
+
(`liveFrameChecks`, `poser`, `renderFrame(frame)`); deltas are seconds, and the player's `timescale`
|
|
49
54
|
(alias `timeScale`) applies exactly once. The app owns the loop. `timescale = 0`
|
|
50
|
-
holds the drawn frame and uploads no geometry. `
|
|
51
|
-
|
|
55
|
+
holds the drawn frame and uploads no geometry. `draw()` returns a detached
|
|
56
|
+
`PosedFrame`; `drawnFrame()` returns a detached snapshot of the last draw,
|
|
57
|
+
including after direct mutations of `rig.player`; `posedFrame(filter)`, `bone(name)` and `bounds()`
|
|
52
58
|
use play's readers. World bounds are in rig-local Y-up space, before this Group's
|
|
53
59
|
transform. `resourceCounts()` reports owned meshes, geometries, page materials,
|
|
54
60
|
clip textures and retained/shared pages. `destroy()` is idempotent and frees
|
|
@@ -64,7 +70,7 @@ both straight and premultiplied pixels: conflicting `pma` records are refused.
|
|
|
64
70
|
## Native drawing and scene placement
|
|
65
71
|
|
|
66
72
|
`ThreeRenderer(root, assets, webglRenderer)` is available when the app owns the
|
|
67
|
-
player. Call `render(player.posedFrame())
|
|
73
|
+
player. Call `render(player.liveFrame())` (or `render(player.posedFrame())`), then render the scene normally.
|
|
68
74
|
No internal canvas, offscreen bitmap of the rig, or fallback renderer draws it.
|
|
69
75
|
Positions and page UVs come directly from the record; the full atlas page is
|
|
70
76
|
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,293 @@
|
|
|
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
|
+
// Only a numeric token survives release: no borrowed attachment or buffer
|
|
67
|
+
// reference is retained to identify an immutable metadata row.
|
|
68
|
+
validated = /* @__PURE__ */ new WeakMap();
|
|
69
|
+
nextIdentity = 1;
|
|
70
|
+
read(frame) {
|
|
71
|
+
void frame.generation;
|
|
72
|
+
const { drawOrder, vertices, tint, attachments, count } = frame;
|
|
73
|
+
const owner = this.owner;
|
|
74
|
+
numbers(drawOrder, owner, "drawOrder");
|
|
75
|
+
numbers(vertices, owner, "vertices");
|
|
76
|
+
numbers(tint, owner, "tint");
|
|
77
|
+
if (!Number.isInteger(count) || count < 0 || count > drawOrder.length) {
|
|
78
|
+
throw new Error(`${owner}: live frame count=${count} needs an integer in 0..${drawOrder.length}`);
|
|
79
|
+
}
|
|
80
|
+
for (let i = 0; i < count; i++) {
|
|
81
|
+
const index = drawOrder[i];
|
|
82
|
+
const attachment = attachments[index];
|
|
83
|
+
if (!Number.isInteger(index) || !attachment || attachment.kind !== "region" && attachment.kind !== "mesh") {
|
|
84
|
+
throw new Error(`${owner}: live drawOrder[${i}]=${index} needs a region or mesh attachment`);
|
|
85
|
+
}
|
|
86
|
+
const a = attachment;
|
|
87
|
+
let identity2 = this.validated.get(a) ?? 0;
|
|
88
|
+
if (identity2 === 0) {
|
|
89
|
+
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)) {
|
|
90
|
+
throw new Error(`${owner}: live slot "${a.slot}" needs named slot, page and region metadata`);
|
|
91
|
+
}
|
|
92
|
+
if (!Number.isInteger(a.offset) || !Number.isInteger(a.count) || a.offset < 0 || a.count < 0 || !Number.isInteger(a.slotIndex) || a.slotIndex < 0) {
|
|
93
|
+
throw new Error(`${owner}: live slot "${a.slot}" has invalid vertex span ${a.offset}+${a.count} or tint slotIndex=${a.slotIndex}`);
|
|
94
|
+
}
|
|
95
|
+
if (frozenData(a)) {
|
|
96
|
+
identity2 = this.nextIdentity++;
|
|
97
|
+
this.validated.set(a, identity2);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
numbers(a.uvs, owner, "uvs", a.slot);
|
|
101
|
+
numbers(a.triangles, owner, "triangles", a.slot);
|
|
102
|
+
if (a.dark !== null) numbers(a.dark, owner, "dark", a.slot);
|
|
103
|
+
if (a.clip !== null) numbers(a.clip.polygon, owner, "clip.polygon", a.slot);
|
|
104
|
+
if (a.offset + a.count > vertices.length || a.slotIndex * 4 + 4 > tint.length) {
|
|
105
|
+
throw new Error(`${owner}: live slot "${a.slot}" has invalid vertex span ${a.offset}+${a.count} or tint slotIndex=${a.slotIndex}`);
|
|
106
|
+
}
|
|
107
|
+
let row = this.rows[i];
|
|
108
|
+
if (!row || row.slot.kind !== a.kind || row.points.count !== a.count) {
|
|
109
|
+
const points = new NumberSpan(a.count);
|
|
110
|
+
const tint2 = new NumberSpan(4);
|
|
111
|
+
const common = { slot: a.slot, blend: a.blend, tint: tint2, page: a.page ?? "", pma: a.pma, uvs: EMPTY };
|
|
112
|
+
const slot2 = a.kind === "region" ? { ...common, kind: "region", region: a.region ?? "", corners: points } : { ...common, kind: "mesh", vertices: points, triangles: EMPTY };
|
|
113
|
+
row = { slot: slot2, points, tint: tint2, identity: 0 };
|
|
114
|
+
this.rows[i] = row;
|
|
115
|
+
}
|
|
116
|
+
row.points.source = vertices;
|
|
117
|
+
row.tint.source = tint;
|
|
118
|
+
const slot = row.slot;
|
|
119
|
+
if (identity2 === 0 || row.identity !== identity2) {
|
|
120
|
+
row.points.offset = a.offset;
|
|
121
|
+
row.tint.offset = a.slotIndex * 4;
|
|
122
|
+
slot.slot = a.slot;
|
|
123
|
+
slot.blend = a.blend;
|
|
124
|
+
slot.page = a.page ?? "";
|
|
125
|
+
slot.pma = a.pma;
|
|
126
|
+
if (slot.kind === "region") slot.region = a.region ?? "";
|
|
127
|
+
row.identity = identity2;
|
|
128
|
+
}
|
|
129
|
+
slot.uvs = a.uvs;
|
|
130
|
+
slot.dark = a.dark ?? void 0;
|
|
131
|
+
slot.clip = a.clip ?? void 0;
|
|
132
|
+
if (slot.kind === "mesh") slot.triangles = a.triangles;
|
|
133
|
+
this.order[i] = slot;
|
|
134
|
+
}
|
|
135
|
+
this.order.length = count;
|
|
136
|
+
return this.order;
|
|
137
|
+
}
|
|
138
|
+
release() {
|
|
139
|
+
for (const row of this.rows) {
|
|
140
|
+
row.points.source = EMPTY;
|
|
141
|
+
row.tint.source = EMPTY;
|
|
142
|
+
row.slot.uvs = EMPTY;
|
|
143
|
+
row.slot.dark = void 0;
|
|
144
|
+
row.slot.clip = void 0;
|
|
145
|
+
if (row.slot.kind === "mesh") row.slot.triangles = EMPTY;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
function identity(a) {
|
|
150
|
+
return JSON.stringify([a.slotIndex, a.slot, a.skin, a.placeholder, a.attachment, a.kind, a.page, a.region]);
|
|
151
|
+
}
|
|
152
|
+
function templateFromAttachment(a) {
|
|
153
|
+
const common = {
|
|
154
|
+
slot: a.slot,
|
|
155
|
+
z: a.slotIndex,
|
|
156
|
+
blend: a.blend,
|
|
157
|
+
tint: [1, 1, 1, 1]
|
|
158
|
+
};
|
|
159
|
+
return a.kind === "region" ? {
|
|
160
|
+
...common,
|
|
161
|
+
kind: "region",
|
|
162
|
+
region: a.region ?? "",
|
|
163
|
+
corners: [],
|
|
164
|
+
page: a.page ?? "",
|
|
165
|
+
pma: a.pma,
|
|
166
|
+
uvs: Array.from(a.uvs)
|
|
167
|
+
} : {
|
|
168
|
+
...common,
|
|
169
|
+
kind: "mesh",
|
|
170
|
+
page: a.page ?? "",
|
|
171
|
+
pma: a.pma,
|
|
172
|
+
vertices: [],
|
|
173
|
+
uvs: Array.from(a.uvs),
|
|
174
|
+
triangles: Array.from(a.triangles),
|
|
175
|
+
sequence: 0
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function copyNumbers(target, source) {
|
|
179
|
+
if (ArrayBuffer.isView(source) && source instanceof Float64Array) {
|
|
180
|
+
target.set(source);
|
|
181
|
+
} else {
|
|
182
|
+
for (let i = 0; i < target.length; i++) target[i] = source[i];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function savedNumbers(source, offset, count) {
|
|
186
|
+
const result = new Array(count);
|
|
187
|
+
for (let i = 0; i < count; i++) result[i] = source[offset + i];
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
var RetainedRuntimeFrame = class {
|
|
191
|
+
constructor(owner = "PixiRig") {
|
|
192
|
+
this.owner = owner;
|
|
193
|
+
}
|
|
194
|
+
owner;
|
|
195
|
+
templates = /* @__PURE__ */ new WeakMap();
|
|
196
|
+
immutable = /* @__PURE__ */ new WeakSet();
|
|
197
|
+
checkedTemplates = /* @__PURE__ */ new Map();
|
|
198
|
+
rows = [];
|
|
199
|
+
vertices = new Float64Array(0);
|
|
200
|
+
tint = new Float64Array(0);
|
|
201
|
+
count = 0;
|
|
202
|
+
generation = 0;
|
|
203
|
+
retained = null;
|
|
204
|
+
capture(frame, seed) {
|
|
205
|
+
let snapshot = null;
|
|
206
|
+
const { count, attachments, drawOrder, vertices, tint } = frame;
|
|
207
|
+
if (this.vertices.length !== vertices.length) this.vertices = new Float64Array(vertices.length);
|
|
208
|
+
if (this.tint.length !== tint.length) this.tint = new Float64Array(tint.length);
|
|
209
|
+
copyNumbers(this.vertices, vertices);
|
|
210
|
+
copyNumbers(this.tint, tint);
|
|
211
|
+
for (let i = 0; i < count; i++) {
|
|
212
|
+
const a = attachments[drawOrder[i]];
|
|
213
|
+
let template = this.immutable.has(a) ? this.templates.get(a) : void 0;
|
|
214
|
+
if (!template) {
|
|
215
|
+
const key = identity(a);
|
|
216
|
+
template = this.checkedTemplates.get(key);
|
|
217
|
+
if (!template) {
|
|
218
|
+
if (seed) {
|
|
219
|
+
snapshot ??= seed();
|
|
220
|
+
template = snapshot.order[i];
|
|
221
|
+
if (!template || template.slot !== a.slot || template.kind !== a.kind || template.page !== a.page) {
|
|
222
|
+
throw new Error(`${this.owner}: live slot "${a.slot}" needs a matching snapshot metadata seed at draw index ${i}`);
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
template = templateFromAttachment(a);
|
|
226
|
+
}
|
|
227
|
+
if (this.checkedTemplates.size >= Math.max(64, frame.attachments.length * 2)) this.checkedTemplates.clear();
|
|
228
|
+
this.checkedTemplates.set(key, template);
|
|
229
|
+
}
|
|
230
|
+
if (frozenData(a)) {
|
|
231
|
+
this.immutable.add(a);
|
|
232
|
+
this.templates.set(a, template);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
let row = this.rows[i];
|
|
236
|
+
if (!row) {
|
|
237
|
+
row = { template, offset: a.offset, count: a.count, tintOffset: a.slotIndex * 4 };
|
|
238
|
+
this.rows[i] = row;
|
|
239
|
+
}
|
|
240
|
+
row.template = template;
|
|
241
|
+
row.offset = a.offset;
|
|
242
|
+
row.count = a.count;
|
|
243
|
+
row.tintOffset = a.slotIndex * 4;
|
|
244
|
+
if (a.dark === null) row.dark = void 0;
|
|
245
|
+
else {
|
|
246
|
+
const dark = row.darkBuffer ??= [];
|
|
247
|
+
dark.length = a.dark.length;
|
|
248
|
+
for (let j = 0; j < dark.length; j++) dark[j] = a.dark[j];
|
|
249
|
+
row.dark = dark;
|
|
250
|
+
}
|
|
251
|
+
if (a.clip === null) row.clip = void 0;
|
|
252
|
+
else {
|
|
253
|
+
const clip = row.clipBuffer ??= { polygon: [], inverse: false, convex: true };
|
|
254
|
+
clip.polygon.length = a.clip.polygon.length;
|
|
255
|
+
for (let j = 0; j < clip.polygon.length; j++) clip.polygon[j] = a.clip.polygon[j];
|
|
256
|
+
clip.inverse = a.clip.inverse;
|
|
257
|
+
clip.convex = a.clip.convex;
|
|
258
|
+
row.clip = clip;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
this.count = frame.count;
|
|
262
|
+
this.generation = frame.generation;
|
|
263
|
+
this.retained = null;
|
|
264
|
+
}
|
|
265
|
+
snapshot() {
|
|
266
|
+
if (this.retained) return this.retained;
|
|
267
|
+
const order = [];
|
|
268
|
+
for (let i = 0; i < this.count; i++) {
|
|
269
|
+
const { template, offset, count, tintOffset, dark, clip } = this.rows[i];
|
|
270
|
+
const common = { ...template, tint: savedNumbers(this.tint, tintOffset, 4) };
|
|
271
|
+
if (dark) common.dark = Array.from(dark);
|
|
272
|
+
else delete common.dark;
|
|
273
|
+
if (clip) common.clip = {
|
|
274
|
+
polygon: Array.from(clip.polygon),
|
|
275
|
+
inverse: clip.inverse,
|
|
276
|
+
convex: clip.convex
|
|
277
|
+
};
|
|
278
|
+
else delete common.clip;
|
|
279
|
+
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 });
|
|
280
|
+
}
|
|
281
|
+
this.retained = { order };
|
|
282
|
+
Object.defineProperty(this.retained, "generation", { value: this.generation, enumerable: false, configurable: true });
|
|
283
|
+
return this.retained;
|
|
284
|
+
}
|
|
285
|
+
/** Explicit draw() already requested a detached snapshot. */
|
|
286
|
+
retain(frame) {
|
|
287
|
+
this.retained = frame;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
1
291
|
// packages/three/src/three.ts
|
|
2
292
|
import { RigPlayer } from "@rig-c/play";
|
|
3
293
|
import {
|
|
@@ -39,11 +329,12 @@ function hull(points) {
|
|
|
39
329
|
};
|
|
40
330
|
return [...half(sorted).slice(0, -1), ...half([...sorted].reverse()).slice(0, -1)];
|
|
41
331
|
}
|
|
42
|
-
function clipPolygon(clip, detail, capacity) {
|
|
332
|
+
function clipPolygon(clip, detail, capacity, indexed = false) {
|
|
43
333
|
const fail = (why) => {
|
|
44
334
|
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
335
|
};
|
|
46
|
-
|
|
336
|
+
const validPolygon = indexed ? Boolean(clip && clip.polygon && typeof clip.polygon === "object" && Number.isInteger(clip.polygon.length)) : Boolean(clip && Array.isArray(clip.polygon));
|
|
337
|
+
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
338
|
if (clip.polygon.length / 2 > capacity) fail(`THREE_CLIP_CAPACITY: found ${clip.polygon.length / 2} vertices`);
|
|
48
339
|
const points = [];
|
|
49
340
|
for (let i = 0; i < clip.polygon.length; i += 2) {
|
|
@@ -80,7 +371,7 @@ var livePages = /* @__PURE__ */ new Set();
|
|
|
80
371
|
var getLivePageTextureCount = () => livePages.size;
|
|
81
372
|
var getPageRefCount = (image) => pages.get(image)?.holders.size ?? 0;
|
|
82
373
|
var getSharedPageTexture = (image) => pages.get(image)?.texture;
|
|
83
|
-
var REGION_INDICES = [0, 1, 2, 2, 3, 0];
|
|
374
|
+
var REGION_INDICES = Object.freeze([0, 1, 2, 2, 3, 0]);
|
|
84
375
|
var VERTEX = `
|
|
85
376
|
out vec2 vUV;
|
|
86
377
|
out vec2 vPosition;
|
|
@@ -137,6 +428,16 @@ var ThreeRenderer = class {
|
|
|
137
428
|
pool = /* @__PURE__ */ new Map();
|
|
138
429
|
materials = /* @__PURE__ */ new Map();
|
|
139
430
|
retained = /* @__PURE__ */ new Set();
|
|
431
|
+
liveViews = new LiveDrawViews("ThreeRenderer");
|
|
432
|
+
retainedRuntimeFrame = new RetainedRuntimeFrame("ThreeRenderer");
|
|
433
|
+
accepted = null;
|
|
434
|
+
hasRetained = false;
|
|
435
|
+
get drawnFrame() {
|
|
436
|
+
if (this.accepted) return this.accepted;
|
|
437
|
+
if (!this.hasRetained) return null;
|
|
438
|
+
this.accepted = this.retainedRuntimeFrame.snapshot();
|
|
439
|
+
return this.accepted;
|
|
440
|
+
}
|
|
140
441
|
get geometryUploadCount() {
|
|
141
442
|
return this.positionUploadCount + this.uvUploadCount + this.triangleUploadCount;
|
|
142
443
|
}
|
|
@@ -150,7 +451,7 @@ var ThreeRenderer = class {
|
|
|
150
451
|
const image = this.assets.pageImages.get(name);
|
|
151
452
|
return image && this.retained.has(image) ? pages.get(image)?.texture : void 0;
|
|
152
453
|
}
|
|
153
|
-
prepare(slot) {
|
|
454
|
+
prepare(slot, key, indexed) {
|
|
154
455
|
const detail = `ThreeRenderer: slot "${slot?.slot ?? "<unnamed>"}" on page "${slot?.page ?? "<unnamed>"}"`;
|
|
155
456
|
const fail = (why) => {
|
|
156
457
|
throw new Error(`${detail}: ${why}`);
|
|
@@ -159,26 +460,58 @@ var ThreeRenderer = class {
|
|
|
159
460
|
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
461
|
if (!["Normal", "Additive", "Multiply", "Screen"].includes(slot.blend)) fail(`blend=${slot.blend} (expected Normal, Additive, Multiply, Screen)`);
|
|
161
462
|
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)`);
|
|
463
|
+
if (!indexed && !Number.isFinite(slot.z)) fail(`z=${String(slot.z)} (expected finite draw-order index)`);
|
|
163
464
|
const image = this.assets.pageImages.get(slot.page);
|
|
164
465
|
if (!image || !(image.naturalWidth > 0 && image.naturalHeight > 0)) fail("page image absent or not decoded (expected positive naturalWidth/naturalHeight)");
|
|
165
466
|
const valid = (values, count, field) => {
|
|
166
|
-
|
|
167
|
-
|
|
467
|
+
const source = numberSource(values), offset = numberOffset(values), length = numberCount(values);
|
|
468
|
+
if (!indexed && !Array.isArray(values) || length !== count) fail(`${field} length=${Array.isArray(values) || indexed ? length : void 0} (expected ${count})`);
|
|
469
|
+
for (let i = 0; i < count; i++) {
|
|
470
|
+
const value = source[offset + i];
|
|
471
|
+
if (!Number.isFinite(value) || !Number.isFinite(Math.fround(value))) fail(`${field}[${i}]=${String(value)} (expected finite float32)`);
|
|
472
|
+
}
|
|
168
473
|
};
|
|
169
474
|
valid(slot.tint, 4, "tint");
|
|
170
475
|
if (slot.dark !== void 0) valid(slot.dark, 3, "dark");
|
|
171
476
|
const coords = slot.kind === "region" ? slot.corners : slot.vertices;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
valid(slot.
|
|
477
|
+
const coordCount = numberCount(coords);
|
|
478
|
+
if (!indexed && !Array.isArray(coords) || coordCount < 6 || coordCount % 2 !== 0) fail(`positions length=${Array.isArray(coords) || indexed ? coordCount : void 0} (expected even length >=6)`);
|
|
479
|
+
valid(coords, slot.kind === "region" ? 8 : coordCount, slot.kind === "region" ? "corners" : "vertices");
|
|
480
|
+
const prior = this.pool.get(key);
|
|
481
|
+
const priorPositions = prior?.mesh.geometry.getAttribute("position");
|
|
482
|
+
const sameVertexCount = !this.alwaysReuploadGeometry && prior !== void 0 && priorPositions !== void 0 && priorPositions.array.length === coordCount / 2 * 3;
|
|
483
|
+
if (!sameVertexCount || !isImmutableSpan(slot.uvs, indexed) || prior.uvs !== slot.uvs) {
|
|
484
|
+
valid(slot.uvs, coordCount, "uvs");
|
|
485
|
+
}
|
|
175
486
|
const indices = slot.kind === "region" ? REGION_INDICES : slot.triangles;
|
|
176
|
-
if (!
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
487
|
+
if (!sameVertexCount || !isImmutableSpan(indices, indexed) || prior.indices !== indices) {
|
|
488
|
+
if (!indexed && !Array.isArray(indices) || indices.length < 3 || indices.length % 3 !== 0) fail(`triangles length=${indices?.length} (expected multiple of three >=3)`);
|
|
489
|
+
for (let i = 0; i < indices.length; i++) {
|
|
490
|
+
const v = indices[i];
|
|
491
|
+
if (!Number.isInteger(v) || v < 0 || v >= coordCount / 2) fail(`triangles[${i}]=${v} (expected integer 0..${coordCount / 2 - 1})`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
if (!indexed && slot.kind === "mesh" && !Number.isInteger(slot.sequence)) fail(`sequence=${slot.sequence} (expected integer)`);
|
|
495
|
+
const tintSource = numberSource(slot.tint), tintOffset = numberOffset(slot.tint);
|
|
496
|
+
const light = [
|
|
497
|
+
tintSource[tintOffset + 0],
|
|
498
|
+
tintSource[tintOffset + 1],
|
|
499
|
+
tintSource[tintOffset + 2],
|
|
500
|
+
tintSource[tintOffset + 3]
|
|
501
|
+
];
|
|
502
|
+
const dark = slot.dark ? [slot.dark[0], slot.dark[1], slot.dark[2]] : [0, 0, 0];
|
|
503
|
+
return {
|
|
504
|
+
slot,
|
|
505
|
+
key,
|
|
506
|
+
image,
|
|
507
|
+
positions: coords,
|
|
508
|
+
indices,
|
|
509
|
+
polygon: slot.clip !== void 0 ? clipPolygon(slot.clip, detail, this.host.capabilities.maxTextureSize, indexed) : null,
|
|
510
|
+
light,
|
|
511
|
+
dark,
|
|
512
|
+
inverse: slot.clip?.inverse ?? false,
|
|
513
|
+
blend: slot.blend
|
|
514
|
+
};
|
|
182
515
|
}
|
|
183
516
|
material(item) {
|
|
184
517
|
let material = this.materials.get(item.image);
|
|
@@ -216,71 +549,162 @@ var ThreeRenderer = class {
|
|
|
216
549
|
}
|
|
217
550
|
render(frame) {
|
|
218
551
|
if (this.disposed) throw new Error("ThreeRenderer: render called after dispose (expected active renderer)");
|
|
219
|
-
if (!frame ||
|
|
220
|
-
|
|
552
|
+
if (!frame || typeof frame !== "object") throw new Error("ThreeRenderer: frame.order absent (expected PosedSlot array)");
|
|
553
|
+
if ("order" in frame) {
|
|
554
|
+
if (!Array.isArray(frame.order)) throw new Error("ThreeRenderer: frame.order absent (expected PosedSlot array)");
|
|
555
|
+
this.renderOrder(frame.order, false);
|
|
556
|
+
this.accepted = frame;
|
|
557
|
+
this.hasRetained = false;
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
this.renderOrder(this.liveViews.read(frame), true);
|
|
562
|
+
this.retainedRuntimeFrame.capture(frame);
|
|
563
|
+
this.accepted = null;
|
|
564
|
+
this.hasRetained = true;
|
|
565
|
+
} finally {
|
|
566
|
+
this.liveViews.release();
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
renderOrder(order, indexed) {
|
|
570
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
571
|
+
const prepared = order.map((s) => {
|
|
572
|
+
const slotName = typeof s?.slot === "string" && s.slot.length > 0 ? s.slot : "<unnamed>";
|
|
573
|
+
const occurrence = occurrences.get(slotName) ?? 0;
|
|
574
|
+
occurrences.set(slotName, occurrence + 1);
|
|
575
|
+
return this.prepare(s, `${slotName}\0${occurrence}`, indexed);
|
|
576
|
+
});
|
|
221
577
|
const conventions = /* @__PURE__ */ new Map();
|
|
222
578
|
for (const item of prepared) {
|
|
223
579
|
const pma = conventions.get(item.image) ?? pages.get(item.image)?.pma;
|
|
224
580
|
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
581
|
conventions.set(item.image, item.slot.pma);
|
|
226
582
|
}
|
|
227
|
-
const occurrences = /* @__PURE__ */ new Map();
|
|
228
583
|
const active = [];
|
|
229
584
|
prepared.forEach((item, index) => {
|
|
230
|
-
const
|
|
231
|
-
occurrences.set(item.slot.slot, occurrence + 1);
|
|
232
|
-
const key = `${item.slot.slot}\0${occurrence}`;
|
|
585
|
+
const { key } = item;
|
|
233
586
|
let entry = this.pool.get(key);
|
|
234
587
|
if (!entry) {
|
|
235
588
|
const geometry = new BufferGeometry();
|
|
236
589
|
const mesh2 = new Mesh(geometry, this.material(item));
|
|
237
590
|
mesh2.frustumCulled = false;
|
|
238
591
|
mesh2.name = item.slot.slot;
|
|
239
|
-
entry = {
|
|
592
|
+
entry = {
|
|
593
|
+
mesh: mesh2,
|
|
594
|
+
polygon: null,
|
|
595
|
+
polygonValues: [],
|
|
596
|
+
uvs: null,
|
|
597
|
+
indices: null,
|
|
598
|
+
light: item.light,
|
|
599
|
+
dark: item.dark,
|
|
600
|
+
inverse: item.inverse,
|
|
601
|
+
blend: item.blend,
|
|
602
|
+
needsWideIndex: false
|
|
603
|
+
};
|
|
604
|
+
const current = entry;
|
|
605
|
+
mesh2.onBeforeRender = () => {
|
|
606
|
+
const material = mesh2.material;
|
|
607
|
+
material.uniforms.light.value = current.light;
|
|
608
|
+
material.uniforms.dark.value = current.dark;
|
|
609
|
+
material.uniforms.polygon.value = current.polygon ?? material.uniforms.page.value;
|
|
610
|
+
material.uniforms.points.value = current.polygon === null ? 0 : current.polygonValues.length / 2;
|
|
611
|
+
material.uniforms.inverse.value = current.inverse;
|
|
612
|
+
material.blendEquation = material.blendEquationAlpha = AddEquation;
|
|
613
|
+
material.blendSrc = current.blend === "Multiply" ? DstColorFactor : OneFactor;
|
|
614
|
+
material.blendDst = current.blend === "Additive" ? OneFactor : current.blend === "Screen" ? OneMinusSrcColorFactor : OneMinusSrcAlphaFactor;
|
|
615
|
+
material.blendSrcAlpha = OneFactor;
|
|
616
|
+
material.blendDstAlpha = material.blendDst;
|
|
617
|
+
material.uniformsNeedUpdate = true;
|
|
618
|
+
};
|
|
240
619
|
this.pool.set(key, entry);
|
|
241
620
|
}
|
|
242
621
|
const { mesh } = entry;
|
|
622
|
+
const positionCount = numberCount(item.positions);
|
|
243
623
|
const oldPositions = mesh.geometry.getAttribute("position"), oldUVs = mesh.geometry.getAttribute("uv"), oldIndex = mesh.geometry.getIndex();
|
|
244
|
-
|
|
245
|
-
if (
|
|
624
|
+
let needsWideIndex = entry.needsWideIndex;
|
|
625
|
+
if (this.alwaysReuploadGeometry || entry.indices !== item.indices || !oldIndex) {
|
|
626
|
+
needsWideIndex = false;
|
|
627
|
+
if (positionCount / 2 > 65535) {
|
|
628
|
+
for (let i = 0; i < item.indices.length; i++) if (item.indices[i] >= 65535) {
|
|
629
|
+
needsWideIndex = true;
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
entry.needsWideIndex = needsWideIndex;
|
|
634
|
+
}
|
|
635
|
+
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
636
|
mesh.geometry.dispose();
|
|
247
637
|
mesh.geometry = new BufferGeometry();
|
|
248
638
|
entry.uvs = entry.indices = null;
|
|
249
639
|
}
|
|
250
|
-
|
|
640
|
+
entry.light = item.light;
|
|
641
|
+
entry.dark = item.dark;
|
|
642
|
+
entry.inverse = item.inverse;
|
|
643
|
+
entry.blend = item.blend;
|
|
644
|
+
if (!indexed) {
|
|
645
|
+
mesh.posedSlot = item.slot;
|
|
646
|
+
} else {
|
|
647
|
+
const prior = mesh.posedSlot;
|
|
648
|
+
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) {
|
|
649
|
+
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 };
|
|
650
|
+
}
|
|
651
|
+
}
|
|
251
652
|
mesh.material = this.material(item);
|
|
252
653
|
mesh.renderOrder = index;
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
654
|
+
const posLength = positionCount / 2 * 3;
|
|
655
|
+
let posAttr = mesh.geometry.getAttribute("position");
|
|
656
|
+
let posChanged = this.alwaysReuploadGeometry || !posAttr || posAttr.array.length !== posLength;
|
|
657
|
+
if (!posAttr || posAttr.array.length !== posLength) {
|
|
658
|
+
posAttr = new BufferAttribute(new Float32Array(posLength), 3).setUsage(DynamicDrawUsage);
|
|
659
|
+
mesh.geometry.setAttribute("position", posAttr);
|
|
660
|
+
}
|
|
661
|
+
const posArray = posAttr.array;
|
|
662
|
+
const posSource = numberSource(item.positions), posOffset = numberOffset(item.positions);
|
|
663
|
+
for (let i = 0, out = 0; i < positionCount; i += 2, out += 3) {
|
|
664
|
+
const x = Math.fround(posSource[posOffset + i]), y = Math.fround(posSource[posOffset + i + 1]);
|
|
665
|
+
if (posArray[out] !== x || posArray[out + 1] !== y || posArray[out + 2] !== 0) {
|
|
666
|
+
posArray[out] = x;
|
|
667
|
+
posArray[out + 1] = y;
|
|
668
|
+
posArray[out + 2] = 0;
|
|
669
|
+
posChanged = true;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (posChanged) {
|
|
673
|
+
posAttr.needsUpdate = true;
|
|
674
|
+
this.positionUploadCount++;
|
|
675
|
+
}
|
|
676
|
+
if (this.alwaysReuploadGeometry || entry.uvs !== item.slot.uvs) {
|
|
677
|
+
const uvs = item.slot.uvs;
|
|
678
|
+
let uvAttr = mesh.geometry.getAttribute("uv");
|
|
679
|
+
let uvChanged = true;
|
|
680
|
+
if (!uvAttr || uvAttr.array.length !== uvs.length) {
|
|
681
|
+
uvAttr = new BufferAttribute(new Float32Array(uvs.length), 2).setUsage(DynamicDrawUsage);
|
|
682
|
+
mesh.geometry.setAttribute("uv", uvAttr);
|
|
259
683
|
}
|
|
260
|
-
const
|
|
261
|
-
for (let i = 0; i <
|
|
262
|
-
|
|
263
|
-
|
|
684
|
+
const uvArray = uvAttr.array;
|
|
685
|
+
for (let i = 0; i < uvs.length; i++) {
|
|
686
|
+
const v = Math.fround(uvs[i]);
|
|
687
|
+
if (uvArray[i] !== v) {
|
|
688
|
+
uvArray[i] = v;
|
|
689
|
+
uvChanged = true;
|
|
690
|
+
}
|
|
264
691
|
}
|
|
265
|
-
if (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
else this.uvUploadCount++;
|
|
692
|
+
if (uvChanged) {
|
|
693
|
+
uvAttr.needsUpdate = true;
|
|
694
|
+
this.uvUploadCount++;
|
|
269
695
|
}
|
|
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;
|
|
696
|
+
entry.uvs = uvs;
|
|
277
697
|
}
|
|
278
698
|
if (this.alwaysReuploadGeometry || entry.indices !== item.indices) {
|
|
279
699
|
const before = mesh.geometry.getIndex();
|
|
280
700
|
if (before && before.count === item.indices.length) {
|
|
281
701
|
for (let i = 0; i < item.indices.length; i++) before.setX(i, item.indices[i]);
|
|
282
702
|
before.needsUpdate = true;
|
|
283
|
-
} else
|
|
703
|
+
} else {
|
|
704
|
+
const copied = new Array(item.indices.length);
|
|
705
|
+
for (let i = 0; i < item.indices.length; i++) copied[i] = item.indices[i];
|
|
706
|
+
mesh.geometry.setIndex(copied);
|
|
707
|
+
}
|
|
284
708
|
this.triangleUploadCount++;
|
|
285
709
|
entry.indices = item.indices;
|
|
286
710
|
}
|
|
@@ -300,21 +724,6 @@ var ThreeRenderer = class {
|
|
|
300
724
|
entry.polygon = null;
|
|
301
725
|
entry.polygonValues = [];
|
|
302
726
|
}
|
|
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
727
|
active.push(mesh);
|
|
319
728
|
});
|
|
320
729
|
for (const entry of this.pool.values()) if (entry.mesh.parent === this.root) this.root.remove(entry.mesh);
|
|
@@ -361,7 +770,13 @@ var ThreeRig = class extends Group {
|
|
|
361
770
|
if (!assets?.document || !(assets.pageImages instanceof Map) || !isWebGLHost(options?.renderer)) throw new Error("ThreeRig: expected ModelAssets and options.renderer WebGLRenderer");
|
|
362
771
|
const scale = options.timescale ?? options.timeScale ?? 1;
|
|
363
772
|
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, {
|
|
773
|
+
this.player = new RigPlayer(document.createElement("div"), assets, {
|
|
774
|
+
mode: assets.regions === "none" ? "canvas" : "dom",
|
|
775
|
+
skin: options.skin,
|
|
776
|
+
slots: options.slots,
|
|
777
|
+
liveFrameChecks: options.liveFrameChecks,
|
|
778
|
+
poser: options.poser
|
|
779
|
+
});
|
|
365
780
|
this.renderer = new ThreeRenderer(this, assets, options.renderer);
|
|
366
781
|
this.timescale = scale;
|
|
367
782
|
try {
|
|
@@ -375,7 +790,7 @@ var ThreeRig = class extends Group {
|
|
|
375
790
|
assets;
|
|
376
791
|
player;
|
|
377
792
|
renderer;
|
|
378
|
-
lastFrame;
|
|
793
|
+
lastFrame = null;
|
|
379
794
|
destroyed = false;
|
|
380
795
|
active() {
|
|
381
796
|
if (this.destroyed) throw new Error("ThreeRig: called after destroy (expected active rig)");
|
|
@@ -383,20 +798,20 @@ var ThreeRig = class extends Group {
|
|
|
383
798
|
play(name, options = {}) {
|
|
384
799
|
this.active();
|
|
385
800
|
this.player.play(name, options);
|
|
386
|
-
this.
|
|
801
|
+
this.drawCurrent();
|
|
387
802
|
}
|
|
388
803
|
seek(time) {
|
|
389
804
|
this.active();
|
|
390
805
|
if (!Number.isFinite(time)) throw new Error(`ThreeRig: time=${time} (expected finite seconds)`);
|
|
391
806
|
this.player.seek(time);
|
|
392
|
-
this.
|
|
807
|
+
this.drawCurrent();
|
|
393
808
|
}
|
|
394
809
|
advance(dt) {
|
|
395
810
|
this.active();
|
|
396
811
|
if (!Number.isFinite(dt) || dt < 0) throw new Error(`ThreeRig: dt=${dt} (expected finite seconds >=0)`);
|
|
397
812
|
if (this.timescale === 0) return;
|
|
398
813
|
this.player.advance(dt);
|
|
399
|
-
this.
|
|
814
|
+
this.drawCurrent();
|
|
400
815
|
}
|
|
401
816
|
update(dt) {
|
|
402
817
|
this.advance(dt);
|
|
@@ -408,9 +823,29 @@ var ThreeRig = class extends Group {
|
|
|
408
823
|
this.lastFrame = frame;
|
|
409
824
|
return frame;
|
|
410
825
|
}
|
|
826
|
+
renderFrame(frame) {
|
|
827
|
+
this.active();
|
|
828
|
+
if ("order" in frame) {
|
|
829
|
+
this.renderer.render(frame);
|
|
830
|
+
this.lastFrame = frame;
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (frame.generation !== this.player.generation) {
|
|
834
|
+
throw new Error(`ThreeRig: live frame generation ${frame.generation} needs current player generation ${this.player.generation}`);
|
|
835
|
+
}
|
|
836
|
+
if (this.player.poser !== "runtime") {
|
|
837
|
+
this.renderFrame(this.player.posedFrame());
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
this.renderer.render(frame);
|
|
841
|
+
this.lastFrame = null;
|
|
842
|
+
}
|
|
843
|
+
drawCurrent() {
|
|
844
|
+
this.renderFrame(this.player.poser === "runtime" ? this.player.liveFrame() : this.player.posedFrame());
|
|
845
|
+
}
|
|
411
846
|
drawnFrame() {
|
|
412
847
|
this.active();
|
|
413
|
-
return this.lastFrame;
|
|
848
|
+
return this.lastFrame ??= this.renderer.drawnFrame;
|
|
414
849
|
}
|
|
415
850
|
posedFrame(slots) {
|
|
416
851
|
this.active();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rig-c/three",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"three": "^0.186.1",
|
|
31
|
-
"@rig-c/play": "^1.
|
|
31
|
+
"@rig-c/play": "^1.20.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@rig-c/adapters-shared": "workspace:*",
|