three-vat 2.1.0 → 4.0.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 +22 -19
- package/dist/{carrier-BXaLAPRO.d.ts → carrier-D4pkftSk.d.ts} +180 -95
- package/dist/chunk-BSAJL5PG.js +249 -0
- package/dist/index.d.ts +191 -30
- package/dist/index.js +848 -222
- package/dist/tsl.d.ts +65 -2
- package/dist/tsl.js +134 -80
- package/dist/webgl.d.ts +95 -7
- package/dist/webgl.js +290 -102
- package/package.json +1 -1
- package/dist/chunk-3PWAY6MD.js +0 -211
package/dist/tsl.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Node } from 'three/webgpu';
|
|
2
|
-
import { e as VATClock,
|
|
2
|
+
import { e as VATClock, k as VATPlaybackTexture, b as VATCarrier, V as VAT, h as VATInstance, f as VATCrowd } from './carrier-D4pkftSk.js';
|
|
3
3
|
import 'three';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -14,8 +14,12 @@ import 'three';
|
|
|
14
14
|
declare function getMaxTextureSize(renderer: object): number;
|
|
15
15
|
/** A fluent TSL float node (has `.add`, `.mul`, … via NodeExtensions). */
|
|
16
16
|
type FloatNode = Node<'float'>;
|
|
17
|
+
/** A fluent TSL int node. */
|
|
18
|
+
type IntNode = Node<'int'>;
|
|
17
19
|
/** A fluent TSL vec3 node. */
|
|
18
20
|
type Vec3Node = Node<'vec3'>;
|
|
21
|
+
/** A fluent TSL bool node — a branch condition, and what `.select()` reads. */
|
|
22
|
+
type BoolNode = Node<'bool'>;
|
|
19
23
|
/**
|
|
20
24
|
* A TSL float uniform: a node the graph reads, and a `{ value }` clock the
|
|
21
25
|
* caller sets per frame. Both halves matter — the node is what the decode
|
|
@@ -107,6 +111,28 @@ interface VATNodes {
|
|
|
107
111
|
/** The time uniform in use — set `.value` each frame. */
|
|
108
112
|
time: VATTimeUniform;
|
|
109
113
|
}
|
|
114
|
+
/** The clip texel: which band an instance plays, how long it is, and how fast. */
|
|
115
|
+
interface ClipTexel {
|
|
116
|
+
/** First texture row of the clip's band. */
|
|
117
|
+
startFrame: IntNode;
|
|
118
|
+
/** Rows in the band. */
|
|
119
|
+
frames: FloatNode;
|
|
120
|
+
/** Seconds the band spans. */
|
|
121
|
+
duration: FloatNode;
|
|
122
|
+
/** Rate multiplier. */
|
|
123
|
+
speed: FloatNode;
|
|
124
|
+
}
|
|
125
|
+
/** The playback texel: when this animation began, and how it repeats. */
|
|
126
|
+
interface PlaybackTexel {
|
|
127
|
+
/** Absolute clock time this animation began. In the past, for a desynced crowd. */
|
|
128
|
+
startTime: FloatNode;
|
|
129
|
+
/** {@link LoopMode}, as the number the pack carries. */
|
|
130
|
+
loopMode: FloatNode;
|
|
131
|
+
/** Repeat count, or {@link INFINITE_REPETITIONS}. */
|
|
132
|
+
repetitions: FloatNode;
|
|
133
|
+
/** {@link EndMode}, as the number the pack carries. */
|
|
134
|
+
endMode: FloatNode;
|
|
135
|
+
}
|
|
110
136
|
/**
|
|
111
137
|
* Build TSL decode nodes for a baked VAT, for the WebGPU/TSL renderer path.
|
|
112
138
|
* Shadows work automatically because `positionNode` also feeds the depth pass.
|
|
@@ -143,6 +169,37 @@ type VATDecoded = {
|
|
|
143
169
|
normal: Vec3Node;
|
|
144
170
|
tangent: Vec3Node | null;
|
|
145
171
|
};
|
|
172
|
+
/**
|
|
173
|
+
* One band resolved — the TSL spelling of the GLSL decode's `VatBand` struct:
|
|
174
|
+
* the two rows and the blend between them, plus the two facts those rows cannot
|
|
175
|
+
* be read back out of.
|
|
176
|
+
*
|
|
177
|
+
* @internal The return type of {@link resolveBand}, exported for the structural
|
|
178
|
+
* tests and for nothing else.
|
|
179
|
+
*/
|
|
180
|
+
interface Band {
|
|
181
|
+
row0: IntNode;
|
|
182
|
+
row1: IntNode;
|
|
183
|
+
blend: FloatNode;
|
|
184
|
+
/** Whether the sampling crossed the band's last row back into its first. */
|
|
185
|
+
wraps: BoolNode;
|
|
186
|
+
/** Whether the repetitions have run out and the instance is holding an end pose. */
|
|
187
|
+
finished: BoolNode;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* `resolveVATFrame` (src/instance-playback.ts) as a node graph, for one (clip
|
|
191
|
+
* texel, playback texel) pair — branch for branch with the GLSL decode's
|
|
192
|
+
* `vatBand`. The semantics live there; this transcribes them, and the mode
|
|
193
|
+
* constants come from that module rather than being retyped as literals.
|
|
194
|
+
*
|
|
195
|
+
* A function of the pair rather than of the instance, because the pair is what
|
|
196
|
+
* there are two of: a crossfading instance resolves its outgoing band by
|
|
197
|
+
* calling this a second time, not by transcribing it a second time.
|
|
198
|
+
*
|
|
199
|
+
* @internal Exported for the structural tests, and for nothing else. Not
|
|
200
|
+
* re-exported from `three-vat`.
|
|
201
|
+
*/
|
|
202
|
+
declare function resolveBand(clip: ClipTexel, playback: PlaybackTexel, time: FloatNode): Band;
|
|
146
203
|
/**
|
|
147
204
|
* The decode's arithmetic: what this instance reads at this moment, as nodes —
|
|
148
205
|
* before the vertex-stage writes that place it. See {@link VATDecoded} for what
|
|
@@ -168,6 +225,12 @@ interface CreateVATMeshOptions {
|
|
|
168
225
|
* call make its own is identical on either path.
|
|
169
226
|
*/
|
|
170
227
|
time?: VATTimeUniform;
|
|
228
|
+
/**
|
|
229
|
+
* The GPU's real texture ceiling — {@link getMaxTextureSize} — which the
|
|
230
|
+
* playback texture checks the crowd against, one row per instance. Defaults
|
|
231
|
+
* to `MAX_TEXTURE_SIZE`, a desktop figure (`VATPlaybackTextureOptions`).
|
|
232
|
+
*/
|
|
233
|
+
maxTextureSize?: number;
|
|
171
234
|
}
|
|
172
235
|
/**
|
|
173
236
|
* Turn a baked VAT and a list of instances into a crowd ready to render: an
|
|
@@ -196,4 +259,4 @@ interface CreateVATMeshOptions {
|
|
|
196
259
|
*/
|
|
197
260
|
declare function createVATMesh(vat: VAT, instances: VATInstance[], options?: CreateVATMeshOptions): VATCrowd;
|
|
198
261
|
|
|
199
|
-
export { type CreateVATMeshOptions, type VATDecoded, type VATNodeOptions, type VATNodes, type VATTimeUniform, createVATMesh, getMaxTextureSize, vatDecode, vatNodes };
|
|
262
|
+
export { type Band, type CreateVATMeshOptions, type VATDecoded, type VATNodeOptions, type VATNodes, type VATTimeUniform, createVATMesh, getMaxTextureSize, resolveBand, vatDecode, vatNodes };
|
package/dist/tsl.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isBatchedCarrier, assertVATCarrier, assertBakedNormal } from './chunk-I4STYOD5.js';
|
|
2
|
-
import { INFINITE_REPETITIONS, LoopMode, EndMode, createVATPlaybackTexture, PACK_TEXELS, RIG_TEXELS, RIG_TEXELS_PER_SLOT } from './chunk-
|
|
2
|
+
import { INFINITE_REPETITIONS, LoopMode, EndMode, createVATPlaybackTexture, PACK_TEXELS, RIG_TEXELS, RIG_TEXELS_PER_SLOT } from './chunk-BSAJL5PG.js';
|
|
3
3
|
import { InstancedMesh } from 'three';
|
|
4
|
-
import { uniform, Fn, positionLocal, normalLocal, tangentLocal, positionGeometry, batch, instancedMesh, float, bool, int, vertexIndex, attribute, mat3, tangentGeometry, normalGeometry, vec4, batchIndirectIndex, instanceIndex, hash, mix, textureLoad, ivec2, dot, mat4 } from 'three/tsl';
|
|
4
|
+
import { uniform, Fn, positionLocal, normalLocal, tangentLocal, positionGeometry, batch, instancedMesh, float, bool, int, vertexIndex, attribute, mat3, tangentGeometry, normalGeometry, vec4, batchIndirectIndex, instanceIndex, hash, mix, textureLoad, ivec2, dot, mat4, vec3 } from 'three/tsl';
|
|
5
5
|
|
|
6
6
|
function getMaxTextureSize(renderer) {
|
|
7
7
|
const backend = renderer.backend;
|
|
@@ -13,27 +13,37 @@ function getMaxTextureSize(renderer) {
|
|
|
13
13
|
}
|
|
14
14
|
var packTexel = (texture, field, instance) => textureLoad(texture, ivec2(int(field), instance));
|
|
15
15
|
function texturePlayback(texture, instance) {
|
|
16
|
-
const
|
|
17
|
-
const playback = packTexel(texture, PACK_TEXELS.playback, instance);
|
|
18
|
-
const fade = packTexel(texture, PACK_TEXELS.fade, instance);
|
|
19
|
-
const frames = clip.y;
|
|
16
|
+
const crossfade = packTexel(texture, PACK_TEXELS.crossfade, instance);
|
|
20
17
|
return {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
live: liveBand(
|
|
19
|
+
packTexel(texture, PACK_TEXELS.clip, instance),
|
|
20
|
+
packTexel(texture, PACK_TEXELS.playback, instance)
|
|
21
|
+
),
|
|
22
|
+
crossfadeDuration: crossfade.x,
|
|
23
|
+
outgoing: outgoingBand(
|
|
24
|
+
packTexel(texture, PACK_TEXELS.outgoingClip, instance),
|
|
25
|
+
packTexel(texture, PACK_TEXELS.outgoingPlayback, instance)
|
|
26
|
+
)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function bandTexels(clip, playback, frames, fps) {
|
|
30
|
+
return {
|
|
31
|
+
clip: {
|
|
32
|
+
startFrame: int(clip.x),
|
|
33
|
+
frames,
|
|
34
|
+
duration: frames.div(fps),
|
|
35
|
+
speed: clip.w
|
|
36
|
+
},
|
|
37
|
+
playback: {
|
|
38
|
+
startTime: playback.x,
|
|
39
|
+
loopMode: playback.y,
|
|
40
|
+
repetitions: playback.z,
|
|
41
|
+
endMode: playback.w
|
|
34
42
|
}
|
|
35
43
|
};
|
|
36
44
|
}
|
|
45
|
+
var liveBand = (clip, playback) => bandTexels(clip, playback, clip.y, clip.z);
|
|
46
|
+
var outgoingBand = (clip, playback) => bandTexels(clip, playback, clip.y.max(1), clip.z.max(1));
|
|
37
47
|
var instanceIdOf = (carrier) => isBatchedCarrier(carrier) ? int(batchIndirectIndex) : int(instanceIndex);
|
|
38
48
|
function clipAt(vat, clipIndex) {
|
|
39
49
|
const clip = vat.clips[clipIndex];
|
|
@@ -43,25 +53,27 @@ function clipAt(vat, clipIndex) {
|
|
|
43
53
|
return clip;
|
|
44
54
|
}
|
|
45
55
|
function hashedPlayback(clip, desync, instance) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
56
|
+
const live = {
|
|
57
|
+
clip: {
|
|
58
|
+
startFrame: int(clip.startFrame),
|
|
59
|
+
frames: float(clip.frames),
|
|
60
|
+
duration: float(clip.frames / clip.fps),
|
|
61
|
+
// Everything but the phase comes from the clip's own baked defaults, so a
|
|
62
|
+
// clip baked "once, clamped, at 2x" plays that way here too. A second set
|
|
63
|
+
// of defaults living in this path would be a crowd that animates
|
|
64
|
+
// differently depending on whether anyone wrote the attributes.
|
|
65
|
+
speed: float(clip.speed)
|
|
66
|
+
},
|
|
67
|
+
playback: {
|
|
68
|
+
// Negated, because desync is now a start time in the *past*: an instance
|
|
69
|
+
// that began `desync` seconds ago is that far into its clip already.
|
|
70
|
+
startTime: hash(instance).mul(-desync),
|
|
71
|
+
loopMode: float(clip.loopMode),
|
|
72
|
+
repetitions: float(clip.repetitions),
|
|
73
|
+
endMode: float(clip.endMode)
|
|
74
|
+
}
|
|
64
75
|
};
|
|
76
|
+
return { live, crossfadeDuration: float(0), outgoing: live };
|
|
65
77
|
}
|
|
66
78
|
function vatNodes(vat, options = {}) {
|
|
67
79
|
const { time = uniform(0), carrier } = options;
|
|
@@ -83,46 +95,65 @@ function vatNodes(vat, options = {}) {
|
|
|
83
95
|
}, "vec3");
|
|
84
96
|
return { positionNode: decode(), time };
|
|
85
97
|
}
|
|
86
|
-
function
|
|
87
|
-
const
|
|
88
|
-
if (carrier) assertVATCarrier(carrier, vat);
|
|
89
|
-
const instance = instanceIdOf(carrier);
|
|
90
|
-
const playback = playbackTexture ? texturePlayback(playbackTexture.texture, instance) : hashedPlayback(clipAt(vat, clipIndex), desync, instance);
|
|
91
|
-
const frames = playback.frames;
|
|
98
|
+
function resolveBand(clip, playback, time) {
|
|
99
|
+
const frames = clip.frames;
|
|
92
100
|
const last = frames.sub(1);
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const loops = local.div(playback.duration);
|
|
101
|
+
const local = time.sub(playback.startTime).mul(clip.speed);
|
|
102
|
+
const loops = local.div(clip.duration);
|
|
96
103
|
const started = local.greaterThanEqual(0);
|
|
97
104
|
const finished = started.and(playback.repetitions.notEqual(INFINITE_REPETITIONS)).and(loops.greaterThanEqual(playback.repetitions));
|
|
98
105
|
const isPingPong = playback.loopMode.equal(LoopMode.PingPong);
|
|
99
|
-
const bounce = loops.
|
|
106
|
+
const bounce = loops.sub(loops.mul(0.5).floor().mul(2));
|
|
100
107
|
const pingPongPhase = bounce.lessThan(1).select(bounce, float(2).sub(bounce));
|
|
101
108
|
const endPhase = playback.endMode.equal(EndMode.Clamp).select(float(1), float(0));
|
|
102
109
|
const phase = started.select(
|
|
103
110
|
finished.select(endPhase, isPingPong.select(pingPongPhase, loops.fract())),
|
|
104
111
|
float(0)
|
|
105
112
|
);
|
|
106
|
-
const
|
|
113
|
+
const looping = started.select(
|
|
107
114
|
finished.select(bool(false), isPingPong.select(bool(false), bool(true))),
|
|
108
115
|
bool(false)
|
|
109
116
|
);
|
|
110
|
-
const
|
|
117
|
+
const holds = looping.and(playback.endMode.equal(EndMode.Clamp)).and(playback.repetitions.notEqual(INFINITE_REPETITIONS)).and(loops.floor().add(1).greaterThanEqual(playback.repetitions));
|
|
118
|
+
const wraps = looping.and(holds.not());
|
|
119
|
+
const f = phase.mul(looping.select(frames, last));
|
|
111
120
|
const f0 = f.floor().min(last);
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const bandRow = (offset) => int(offset).add(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
const next = f0.add(1);
|
|
122
|
+
const f1 = wraps.select(next.greaterThanEqual(frames).select(float(0), next), next.min(last));
|
|
123
|
+
const bandRow = (offset) => int(offset).add(clip.startFrame);
|
|
124
|
+
return {
|
|
125
|
+
row0: bandRow(f0),
|
|
126
|
+
row1: bandRow(f1),
|
|
127
|
+
blend: f.sub(f0),
|
|
128
|
+
wraps,
|
|
129
|
+
finished
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function vatDecode(vat, options = {}) {
|
|
133
|
+
const { time = uniform(0), playback: playbackTexture, carrier, clipIndex = 0, desync = 0 } = options;
|
|
134
|
+
if (carrier) assertVATCarrier(carrier, vat);
|
|
135
|
+
const instance = instanceIdOf(carrier);
|
|
136
|
+
const playback = playbackTexture ? texturePlayback(playbackTexture.texture, instance) : hashedPlayback(clipAt(vat, clipIndex), desync, instance);
|
|
137
|
+
const live = resolveBand(playback.live.clip, playback.live.playback, time);
|
|
138
|
+
const elapsed = time.sub(playback.live.playback.startTime);
|
|
139
|
+
const duration = playback.crossfadeDuration;
|
|
140
|
+
const weight = duration.greaterThan(0).select(
|
|
141
|
+
float(1).sub(elapsed.div(duration).clamp(0, 1)),
|
|
120
142
|
float(0)
|
|
121
143
|
);
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
144
|
+
const resolved = resolveBand(playback.outgoing.clip, playback.outgoing.playback, time);
|
|
145
|
+
const blending = weight.greaterThan(0);
|
|
146
|
+
const outgoing = {
|
|
147
|
+
row0: blending.select(resolved.row0, live.row0),
|
|
148
|
+
row1: blending.select(resolved.row1, live.row1),
|
|
149
|
+
blend: blending.select(resolved.blend, live.blend),
|
|
150
|
+
// Not selected, and deliberately: a sampler reads a band's rows and its
|
|
151
|
+
// blend and nothing else, so these two are carried because the band
|
|
152
|
+
// resolver answers them and not because anything downstream asks.
|
|
153
|
+
wraps: resolved.wraps,
|
|
154
|
+
finished: resolved.finished
|
|
155
|
+
};
|
|
156
|
+
const rows = { live, outgoing, weight };
|
|
126
157
|
switch (vat.encoding) {
|
|
127
158
|
case "delta":
|
|
128
159
|
return vertexDecode(vat, rows);
|
|
@@ -136,18 +167,33 @@ function vatDecode(vat, options = {}) {
|
|
|
136
167
|
}
|
|
137
168
|
}
|
|
138
169
|
}
|
|
170
|
+
function octDecode(stored) {
|
|
171
|
+
const e = stored.mul(2).sub(1);
|
|
172
|
+
const ex = e.x;
|
|
173
|
+
const ey = e.y;
|
|
174
|
+
const z = float(1).sub(ex.abs()).sub(ey.abs());
|
|
175
|
+
const overshoot = z.negate().max(0);
|
|
176
|
+
const back = (component) => component.sub(component.greaterThanEqual(0).select(overshoot, overshoot.negate()));
|
|
177
|
+
return vec3(back(ex), back(ey), z).normalize();
|
|
178
|
+
}
|
|
139
179
|
function vertexDecode({ positionTexture, normalTexture }, rows) {
|
|
140
180
|
const vertexRow = int(vertexIndex);
|
|
141
|
-
const
|
|
142
|
-
const s0 = textureLoad(tex, ivec2(vertexRow,
|
|
143
|
-
const s1 = textureLoad(tex, ivec2(vertexRow,
|
|
144
|
-
|
|
145
|
-
return mix(mix(s0, s1, rows.blend), frozen, rows.fadeWeight);
|
|
181
|
+
const band = (tex, of) => {
|
|
182
|
+
const s0 = textureLoad(tex, ivec2(vertexRow, of.row0)).xyz;
|
|
183
|
+
const s1 = textureLoad(tex, ivec2(vertexRow, of.row1)).xyz;
|
|
184
|
+
return mix(s0, s1, of.blend);
|
|
146
185
|
};
|
|
186
|
+
const sample = (tex) => mix(band(tex, rows.live), band(tex, rows.outgoing), rows.weight);
|
|
187
|
+
const bandNormal = (tex, of) => {
|
|
188
|
+
const s0 = octDecode(textureLoad(tex, ivec2(vertexRow, of.row0)).xy);
|
|
189
|
+
const s1 = octDecode(textureLoad(tex, ivec2(vertexRow, of.row1)).xy);
|
|
190
|
+
return mix(s0, s1, of.blend);
|
|
191
|
+
};
|
|
192
|
+
const sampleNormal = (tex) => mix(bandNormal(tex, rows.live), bandNormal(tex, rows.outgoing), rows.weight);
|
|
147
193
|
return {
|
|
148
194
|
encoding: "delta",
|
|
149
195
|
position: sample(positionTexture),
|
|
150
|
-
normal: normalTexture ?
|
|
196
|
+
normal: normalTexture ? sampleNormal(normalTexture).normalize() : null
|
|
151
197
|
};
|
|
152
198
|
}
|
|
153
199
|
function compose(q, ts) {
|
|
@@ -181,21 +227,29 @@ function rigDecode({ rigTexture, geometry }, rows) {
|
|
|
181
227
|
const skinIndex = attribute("skinIndex", "uvec4");
|
|
182
228
|
const skinWeight = attribute("skinWeight", "vec4");
|
|
183
229
|
const fetch = (column, row) => textureLoad(rigTexture, ivec2(column, row));
|
|
230
|
+
const pose = (rotation, placement, of) => {
|
|
231
|
+
const q0 = fetch(rotation, of.row0);
|
|
232
|
+
const ts0 = fetch(placement, of.row0);
|
|
233
|
+
const q1 = hemisphereOf(q0, fetch(rotation, of.row1));
|
|
234
|
+
const ts1 = fetch(placement, of.row1);
|
|
235
|
+
return {
|
|
236
|
+
// A normalised lerp, not a slerp: at a bake's frame step the angular error
|
|
237
|
+
// against a true slerp is far below anything visible. It is still a
|
|
238
|
+
// *rotation* at every blend, which is what a componentwise matrix lerp is
|
|
239
|
+
// not — that one shortens a limb as it turns (ADR-0018).
|
|
240
|
+
q: mix(q0, q1, of.blend).normalize(),
|
|
241
|
+
ts: mix(ts0, ts1, of.blend)
|
|
242
|
+
};
|
|
243
|
+
};
|
|
184
244
|
const slot = (index, weight) => {
|
|
185
245
|
const column = (texel) => int(index).mul(RIG_TEXELS_PER_SLOT).add(texel);
|
|
186
246
|
const rotation = column(RIG_TEXELS.rotation);
|
|
187
247
|
const placement = column(RIG_TEXELS.placement);
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
const ts = mix(ts0, ts1, rows.blend);
|
|
194
|
-
const qf = hemisphereOf(q, fetch(rotation, rows.fadeRow));
|
|
195
|
-
const tsf = fetch(placement, rows.fadeRow);
|
|
196
|
-
const qFaded = mix(q, qf, rows.fadeWeight).normalize();
|
|
197
|
-
const tsFaded = mix(ts, tsf, rows.fadeWeight);
|
|
198
|
-
return compose(qFaded, tsFaded).mul(weight);
|
|
248
|
+
const live = pose(rotation, placement, rows.live);
|
|
249
|
+
const outgoing = pose(rotation, placement, rows.outgoing);
|
|
250
|
+
const q = mix(live.q, hemisphereOf(live.q, outgoing.q), rows.weight).normalize();
|
|
251
|
+
const ts = mix(live.ts, outgoing.ts, rows.weight);
|
|
252
|
+
return compose(q, ts).mul(weight);
|
|
199
253
|
};
|
|
200
254
|
const skin = slot(skinIndex.x, skinWeight.x).add(slot(skinIndex.y, skinWeight.y)).add(slot(skinIndex.z, skinWeight.z)).add(slot(skinIndex.w, skinWeight.w));
|
|
201
255
|
const skin3 = mat3(skin);
|
|
@@ -211,7 +265,7 @@ function rigDecode({ rigTexture, geometry }, rows) {
|
|
|
211
265
|
}
|
|
212
266
|
function createVATMesh(vat, instances, options = {}) {
|
|
213
267
|
const time = options.time ?? uniform(0);
|
|
214
|
-
const playback = createVATPlaybackTexture(instances);
|
|
268
|
+
const playback = createVATPlaybackTexture(instances, { maxTextureSize: options.maxTextureSize });
|
|
215
269
|
for (const source of vat.materials) assertBakedNormal(vat, source);
|
|
216
270
|
const materials = vat.materials.map((source) => source.clone());
|
|
217
271
|
const mesh = new InstancedMesh(vat.geometry, materials, instances.length);
|
|
@@ -220,4 +274,4 @@ function createVATMesh(vat, instances, options = {}) {
|
|
|
220
274
|
return { mesh, time, playback };
|
|
221
275
|
}
|
|
222
276
|
|
|
223
|
-
export { createVATMesh, getMaxTextureSize, vatDecode, vatNodes };
|
|
277
|
+
export { createVATMesh, getMaxTextureSize, resolveBand, vatDecode, vatNodes };
|
package/dist/webgl.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IUniform, MeshDepthMaterial, WebGLRenderer, Material } from 'three';
|
|
2
|
-
import {
|
|
2
|
+
import { b as VATCarrier, V as VAT, k as VATPlaybackTexture, h as VATInstance, f as VATCrowd } from './carrier-D4pkftSk.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The real maximum texture dimension this GPU accepts, for
|
|
@@ -16,6 +16,73 @@ interface VATUniforms {
|
|
|
16
16
|
}
|
|
17
17
|
/** Create the shared time uniform. Update `uVatTime.value` once per frame. */
|
|
18
18
|
declare function createVATUniforms(time?: number): VATUniforms;
|
|
19
|
+
/**
|
|
20
|
+
* The caller's own GLSL, run after the decode has posed the vertex (ADR-0021):
|
|
21
|
+
* a wind sway, a twist toward a target, a per-instance squash — deformation
|
|
22
|
+
* that is the scene's and never the library's. The WebGL path's answer to what
|
|
23
|
+
* `positionNode` already gives the TSL path, which needs none of this.
|
|
24
|
+
*
|
|
25
|
+
* It has **two** injection points because three expands `beginnormal_vertex`
|
|
26
|
+
* *before* `begin_vertex` and derives `transformedNormal` between them: a chunk
|
|
27
|
+
* that only moves the position cannot repair a normal that was already taken,
|
|
28
|
+
* and the crowd would shade as though it had never moved. One point is a hook
|
|
29
|
+
* that looks right in the viewport and is wrong in the light.
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* const hook = {
|
|
33
|
+
* key: 'twist',
|
|
34
|
+
* uniforms: { uTarget: { value: new Vector3() } },
|
|
35
|
+
* prelude: `
|
|
36
|
+
* uniform vec3 uTarget;
|
|
37
|
+
* vec3 twistY( vec3 p, float a ) {
|
|
38
|
+
* float s = sin( a ), c = cos( a );
|
|
39
|
+
* return vec3( c * p.x + s * p.z, p.y, -s * p.x + c * p.z );
|
|
40
|
+
* }`,
|
|
41
|
+
* position: 'transformed = twistY( transformed, vatTwistAngle( vatInstanceIndex ) );',
|
|
42
|
+
* normal: 'objectNormal = twistY( objectNormal, vatTwistAngle( vatInstanceIndex ) );',
|
|
43
|
+
* }
|
|
44
|
+
* const { mesh } = createVATMesh(vat, instances, { hook })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
interface VATPostDecodeHook {
|
|
48
|
+
/**
|
|
49
|
+
* What tells this hook's compiled program from another's. Required, and
|
|
50
|
+
* folded into the library's own key rather than replacing it: without it two
|
|
51
|
+
* crowds whose materials are identical in every parameter three looks at
|
|
52
|
+
* share one program, and one of them renders the other's GLSL (ADR-0006).
|
|
53
|
+
*/
|
|
54
|
+
key: string;
|
|
55
|
+
/**
|
|
56
|
+
* GLSL emitted ahead of three's shader — helper functions, the `uniform`
|
|
57
|
+
* declarations {@link uniforms} binds. Not an injection point, so ADR-0006
|
|
58
|
+
* does not govern it: this is where a helper the two chunks share is declared
|
|
59
|
+
* once.
|
|
60
|
+
*/
|
|
61
|
+
prelude?: string;
|
|
62
|
+
/**
|
|
63
|
+
* The chunk run where three takes the position, with `transformed` in object
|
|
64
|
+
* space and already posed by the decode. Assign to it.
|
|
65
|
+
*/
|
|
66
|
+
position?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The chunk run where three takes the normal, with `objectNormal` already
|
|
69
|
+
* posed. Assign to it — otherwise a deformed crowd shades undeformed.
|
|
70
|
+
*/
|
|
71
|
+
normal?: string;
|
|
72
|
+
/** Uniforms bound beside the library's own, so the chunks can be driven by your game state. */
|
|
73
|
+
uniforms?: Record<string, IUniform>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* What {@link patchVATMaterial} and {@link createVATDepthMaterial} take in
|
|
77
|
+
* place of a bare carrier — so a batched crowd with a hook passes one object
|
|
78
|
+
* rather than a positional carrier plus something else.
|
|
79
|
+
*/
|
|
80
|
+
interface VATPatchOptions {
|
|
81
|
+
/** The mesh this material will draw on. Means exactly what the bare argument means. */
|
|
82
|
+
carrier?: VATCarrier;
|
|
83
|
+
/** The caller's own GLSL, after the decode. */
|
|
84
|
+
hook?: VATPostDecodeHook;
|
|
85
|
+
}
|
|
19
86
|
/**
|
|
20
87
|
* Patch any built-in material so its vertex stage samples the VAT instead of
|
|
21
88
|
* skinning. Works on the render material and on `MeshDepthMaterial` (needed for
|
|
@@ -33,19 +100,26 @@ declare function createVATUniforms(time?: number): VATUniforms;
|
|
|
33
100
|
* instead, because that carrier culls and sorts per instance and its drawn slot
|
|
34
101
|
* is a permutation that changes every frame (ADR-0016). A batch a VAT cannot be
|
|
35
102
|
* decoded on is refused here rather than rendered wrong.
|
|
103
|
+
*
|
|
104
|
+
* That fifth argument also takes a {@link VATPatchOptions} object, which is how
|
|
105
|
+
* a {@link VATPostDecodeHook} is passed — the caller's own GLSL after the
|
|
106
|
+
* decode, and the carrier beside it in one object.
|
|
36
107
|
*/
|
|
37
|
-
declare function patchVATMaterial<T extends Material>(material: T, vat: VAT, uniforms: VATUniforms, playback: VATPlaybackTexture,
|
|
108
|
+
declare function patchVATMaterial<T extends Material>(material: T, vat: VAT, uniforms: VATUniforms, playback: VATPlaybackTexture, options?: VATCarrier | VATPatchOptions): T;
|
|
38
109
|
/**
|
|
39
110
|
* Build the `customDepthMaterial` a VAT crowd needs so it casts
|
|
40
111
|
* correctly-deformed shadows instead of bind-pose shadows. Assign the result to
|
|
41
112
|
* `mesh.customDepthMaterial` (and, for point lights, mirror with a patched
|
|
42
113
|
* `MeshDistanceMaterial`).
|
|
43
114
|
*
|
|
44
|
-
*
|
|
45
|
-
* `InstancedMesh`, pass the `BatchedMesh` for a batched crowd, so the
|
|
46
|
-
* pass resolves the same instance index the render pass does
|
|
115
|
+
* The fourth argument means what it means in {@link patchVATMaterial}: omit it
|
|
116
|
+
* for an `InstancedMesh`, pass the `BatchedMesh` for a batched crowd, so the
|
|
117
|
+
* shadow pass resolves the same instance index the render pass does — or pass
|
|
118
|
+
* the options object, so a hand-wired crowd's shadow deforms with the hook its
|
|
119
|
+
* render material carries. A deformed crowd casting an undeformed shadow is
|
|
120
|
+
* exactly the class of mistake this library exists to take off the caller.
|
|
47
121
|
*/
|
|
48
|
-
declare function createVATDepthMaterial(vat: VAT, uniforms: VATUniforms, playback: VATPlaybackTexture,
|
|
122
|
+
declare function createVATDepthMaterial(vat: VAT, uniforms: VATUniforms, playback: VATPlaybackTexture, options?: VATCarrier | VATPatchOptions): MeshDepthMaterial;
|
|
49
123
|
/** Options for {@link createVATMesh}. */
|
|
50
124
|
interface CreateVATMeshOptions {
|
|
51
125
|
/**
|
|
@@ -55,6 +129,20 @@ interface CreateVATMeshOptions {
|
|
|
55
129
|
* `time`. The TSL path's `vatNodes` takes its clock the same way.
|
|
56
130
|
*/
|
|
57
131
|
time?: IUniform<number>;
|
|
132
|
+
/**
|
|
133
|
+
* Your own GLSL after the decode (ADR-0021), threaded to every material this
|
|
134
|
+
* crowd draws with — the render materials, the depth material and the
|
|
135
|
+
* distance material. Threaded here rather than applied by hand afterwards
|
|
136
|
+
* because omitting one of the three is the bug: a twisted crowd casting an
|
|
137
|
+
* untwisted shadow.
|
|
138
|
+
*/
|
|
139
|
+
hook?: VATPostDecodeHook;
|
|
140
|
+
/**
|
|
141
|
+
* The GPU's real texture ceiling — {@link getMaxTextureSize} — which the
|
|
142
|
+
* playback texture checks the crowd against, one row per instance. Defaults
|
|
143
|
+
* to `MAX_TEXTURE_SIZE`, a desktop figure (`VATPlaybackTextureOptions`).
|
|
144
|
+
*/
|
|
145
|
+
maxTextureSize?: number;
|
|
58
146
|
}
|
|
59
147
|
/**
|
|
60
148
|
* Turn a baked VAT and a list of instances into a crowd ready to render: an
|
|
@@ -90,4 +178,4 @@ interface CreateVATMeshOptions {
|
|
|
90
178
|
*/
|
|
91
179
|
declare function createVATMesh(vat: VAT, instances: VATInstance[], options?: CreateVATMeshOptions): VATCrowd;
|
|
92
180
|
|
|
93
|
-
export { type CreateVATMeshOptions, type VATUniforms, createVATDepthMaterial, createVATMesh, createVATUniforms, getMaxTextureSize, patchVATMaterial };
|
|
181
|
+
export { type CreateVATMeshOptions, type VATPatchOptions, type VATPostDecodeHook, type VATUniforms, createVATDepthMaterial, createVATMesh, createVATUniforms, getMaxTextureSize, patchVATMaterial };
|