reze-engine 0.65.0 → 0.66.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/dist/engine.d.ts +156 -42
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +874 -185
- package/dist/graph/compile.d.ts.map +1 -1
- package/dist/graph/compile.js +5 -0
- package/dist/graph/presets/body.d.ts.map +1 -1
- package/dist/graph/presets/body.js +26 -24
- package/dist/graph/presets/cloth_rough.js +8 -8
- package/dist/graph/presets/cloth_smooth.js +8 -8
- package/dist/graph/presets/default.js +4 -4
- package/dist/graph/presets/eye.js +10 -10
- package/dist/graph/presets/face.d.ts.map +1 -1
- package/dist/graph/presets/face.js +28 -26
- package/dist/graph/presets/hair.js +6 -6
- package/dist/graph/presets/metal.js +8 -8
- package/dist/graph/presets/stockings.d.ts.map +1 -1
- package/dist/graph/presets/stockings.js +4 -11
- package/dist/graph/presets/unlit.d.ts.map +1 -1
- package/dist/graph/presets/unlit.js +9 -13
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +128 -157
- package/dist/graph/render-class.d.ts +5 -2
- package/dist/graph/render-class.d.ts.map +1 -1
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +5 -1
- package/dist/model.d.ts +14 -1
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +27 -1
- package/dist/pmx-loader.d.ts +4 -0
- package/dist/pmx-loader.d.ts.map +1 -1
- package/dist/pmx-loader.js +11 -2
- package/dist/shaders/anchor-table.d.ts +11 -0
- package/dist/shaders/anchor-table.d.ts.map +1 -1
- package/dist/shaders/anchor-table.js +9 -3
- package/dist/shaders/directives.d.ts +42 -5
- package/dist/shaders/directives.d.ts.map +1 -1
- package/dist/shaders/directives.js +51 -4
- package/dist/shaders/lights.d.ts +11 -7
- package/dist/shaders/lights.d.ts.map +1 -1
- package/dist/shaders/lights.js +315 -271
- package/dist/shaders/materials/common.d.ts.map +1 -1
- package/dist/shaders/materials/common.js +26 -3
- package/dist/shaders/materials/nodes.d.ts +1 -1
- package/dist/shaders/materials/nodes.d.ts.map +1 -1
- package/dist/shaders/materials/nodes.js +191 -271
- package/dist/shaders/passes/composite.d.ts +1 -1
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +3 -5
- package/dist/shaders/passes/ground.js +1 -1
- package/dist/shaders/passes/particles.d.ts.map +1 -1
- package/dist/shaders/passes/particles.js +9 -3
- package/dist/shaders/passes/probe.d.ts +45 -0
- package/dist/shaders/passes/probe.d.ts.map +1 -0
- package/dist/shaders/passes/probe.js +230 -0
- package/dist/shaders/passes/scene-contract.d.ts +13 -0
- package/dist/shaders/passes/scene-contract.d.ts.map +1 -1
- package/dist/shaders/passes/scene-contract.js +4 -0
- package/dist/shaders/scene-light-api.d.ts.map +1 -1
- package/dist/shaders/scene-light-api.js +1 -0
- package/dist/unity/globals.d.ts +7 -0
- package/dist/unity/globals.d.ts.map +1 -1
- package/dist/unity/globals.js +4 -3
- package/dist/unity/host.d.ts +3 -0
- package/dist/unity/host.d.ts.map +1 -1
- package/dist/unity/host.js +41 -7
- package/dist/unity/looks.d.ts +42 -0
- package/dist/unity/looks.d.ts.map +1 -1
- package/dist/unity/looks.js +131 -4
- package/dist/unity/stage.d.ts +17 -0
- package/dist/unity/stage.d.ts.map +1 -1
- package/dist/unity/stage.js +22 -5
- package/package.json +1 -1
- package/src/engine.ts +947 -214
- package/src/graph/compile.ts +4 -0
- package/src/graph/presets/body.ts +26 -24
- package/src/graph/presets/cloth_rough.ts +8 -8
- package/src/graph/presets/cloth_smooth.ts +8 -8
- package/src/graph/presets/default.ts +4 -4
- package/src/graph/presets/eye.ts +10 -10
- package/src/graph/presets/face.ts +28 -26
- package/src/graph/presets/hair.ts +6 -6
- package/src/graph/presets/metal.ts +8 -8
- package/src/graph/presets/stockings.ts +4 -11
- package/src/graph/presets/unlit.ts +9 -13
- package/src/graph/registry.ts +133 -158
- package/src/graph/render-class.ts +5 -2
- package/src/graph/slots.ts +5 -1
- package/src/model.ts +33 -1
- package/src/pmx-loader.ts +11 -2
- package/src/shaders/anchor-table.ts +15 -3
- package/src/shaders/directives.ts +88 -6
- package/src/shaders/lights.ts +464 -415
- package/src/shaders/materials/common.ts +26 -3
- package/src/shaders/materials/nodes.ts +191 -271
- package/src/shaders/passes/composite.ts +3 -5
- package/src/shaders/passes/ground.ts +1 -1
- package/src/shaders/passes/particles.ts +9 -3
- package/src/shaders/passes/probe.ts +240 -0
- package/src/shaders/passes/scene-contract.ts +17 -0
- package/src/shaders/scene-light-api.ts +1 -0
- package/src/unity/globals.ts +7 -3
- package/src/unity/host.ts +36 -6
- package/src/unity/looks.ts +161 -7
- package/src/unity/stage.ts +28 -5
- package/dist/physics-debug.d.ts +0 -30
- package/dist/physics-debug.d.ts.map +0 -1
- package/dist/physics-debug.js +0 -526
- package/dist/shaders/agx-lut.d.ts +0 -11
- package/dist/shaders/agx-lut.d.ts.map +0 -1
- package/dist/shaders/agx-lut.js +0 -29
- package/dist/shaders/dfg_lut.d.ts +0 -3
- package/dist/shaders/dfg_lut.d.ts.map +0 -1
- package/dist/shaders/dfg_lut.js +0 -129
- package/dist/shaders/ltc_mag_lut.d.ts +0 -3
- package/dist/shaders/ltc_mag_lut.d.ts.map +0 -1
- package/dist/shaders/ltc_mag_lut.js +0 -1033
- package/dist/shaders/materials/body.d.ts +0 -2
- package/dist/shaders/materials/body.d.ts.map +0 -1
- package/dist/shaders/materials/body.js +0 -95
- package/dist/shaders/materials/cloth_rough.d.ts +0 -2
- package/dist/shaders/materials/cloth_rough.d.ts.map +0 -1
- package/dist/shaders/materials/cloth_rough.js +0 -69
- package/dist/shaders/materials/cloth_smooth.d.ts +0 -2
- package/dist/shaders/materials/cloth_smooth.d.ts.map +0 -1
- package/dist/shaders/materials/cloth_smooth.js +0 -61
- package/dist/shaders/materials/default.d.ts +0 -2
- package/dist/shaders/materials/default.d.ts.map +0 -1
- package/dist/shaders/materials/default.js +0 -43
- package/dist/shaders/materials/eye.d.ts +0 -2
- package/dist/shaders/materials/eye.d.ts.map +0 -1
- package/dist/shaders/materials/eye.js +0 -60
- package/dist/shaders/materials/face.d.ts +0 -2
- package/dist/shaders/materials/face.d.ts.map +0 -1
- package/dist/shaders/materials/face.js +0 -95
- package/dist/shaders/materials/hair.d.ts +0 -2
- package/dist/shaders/materials/hair.d.ts.map +0 -1
- package/dist/shaders/materials/hair.js +0 -90
- package/dist/shaders/materials/metal.d.ts +0 -2
- package/dist/shaders/materials/metal.d.ts.map +0 -1
- package/dist/shaders/materials/metal.js +0 -77
- package/dist/shaders/materials/mmd_classic.d.ts +0 -2
- package/dist/shaders/materials/mmd_classic.d.ts.map +0 -1
- package/dist/shaders/materials/mmd_classic.js +0 -66
- package/dist/shaders/materials/stockings.d.ts +0 -2
- package/dist/shaders/materials/stockings.d.ts.map +0 -1
- package/dist/shaders/materials/stockings.js +0 -122
- package/dist/shaders/passes/physics-debug.d.ts +0 -2
- package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
- package/dist/shaders/passes/physics-debug.js +0 -69
- package/src/shaders/dfg_lut.ts +0 -131
- package/src/shaders/ltc_mag_lut.ts +0 -1035
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
// Unlit — the texture, at its own brightness, and nothing else.
|
|
2
2
|
//
|
|
3
|
-
// What
|
|
4
|
-
// somewhere else
|
|
5
|
-
// element. Shading it is not a stylistic choice but a mistake
|
|
3
|
+
// What a picture needs: a media plane carries pixels somebody already finished
|
|
4
|
+
// somewhere else — a gradient painted in Photoshop, a title card, a rendered
|
|
5
|
+
// element. Shading it is not a stylistic choice but a mistake: the scene's sun
|
|
6
6
|
// would dim one side of a card that has no side, and the world colour would
|
|
7
|
-
// tint artwork whose colour is the point.
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// Emission rather than Principled with roughness 1: emission is radiance, so it
|
|
12
|
-
// leaves the light loop entirely instead of being an unusually flat surface
|
|
13
|
-
// inside it. That is also what lets a card sit in front of a lamp without
|
|
7
|
+
// tint artwork whose colour is the point. Unity's answer is the same one, its
|
|
8
|
+
// Unlit shader: the colour out at the value it was authored at, never in the
|
|
9
|
+
// light loop — which is also what lets a card sit in front of a lamp without
|
|
14
10
|
// picking up its highlight.
|
|
15
11
|
|
|
16
12
|
import type { ShaderGraph } from "../schema"
|
|
@@ -26,12 +22,12 @@ export const UNLIT_GRAPH: ShaderGraph = {
|
|
|
26
22
|
// one of its own.
|
|
27
23
|
{ id: "mat", type: "material_diffuse" },
|
|
28
24
|
{ id: "base", type: "mix/multiply", inputs: { fac: 1.0 } },
|
|
29
|
-
{ id: "emit", type: "
|
|
25
|
+
{ id: "emit", type: "vector_math/scale", inputs: { scale: 1.0 } },
|
|
30
26
|
],
|
|
31
27
|
links: [
|
|
32
28
|
{ from: { node: "tex", socket: "color" }, to: { node: "base", socket: "a" } },
|
|
33
29
|
{ from: { node: "mat", socket: "color" }, to: { node: "base", socket: "b" } },
|
|
34
|
-
{ from: { node: "base", socket: "color" }, to: { node: "emit", socket: "
|
|
30
|
+
{ from: { node: "base", socket: "color" }, to: { node: "emit", socket: "a" } },
|
|
35
31
|
],
|
|
36
|
-
output: { node: "emit", socket: "
|
|
32
|
+
output: { node: "emit", socket: "vector" },
|
|
37
33
|
}
|
package/src/graph/registry.ts
CHANGED
|
@@ -116,18 +116,6 @@ const RAMP_OUTPUTS: Record<string, SockT> = { color: "color", alpha: "float", fa
|
|
|
116
116
|
// routing through the BT.601 color→float conversion instead would change the value.
|
|
117
117
|
const RAMP_SELECT = { color: ".rgb", alpha: ".a", fac_out: ".r" }
|
|
118
118
|
|
|
119
|
-
/** Principled v2 reflectance, evaluated at compile time when it can be. */
|
|
120
|
-
function foldSpecular(ior: string, level: string): string {
|
|
121
|
-
const i = Number(ior)
|
|
122
|
-
const l = Number(level)
|
|
123
|
-
if (!Number.isFinite(i) || !Number.isFinite(l)) return `principled_specular(${ior}, ${level})`
|
|
124
|
-
const r = (i - 1) / Math.max(i + 1, 1e-6)
|
|
125
|
-
// Snapped to 9 significant digits: the arithmetic leaves double noise
|
|
126
|
-
// (0.2² · 2 · 0.5 / 0.08 = 0.5000000000000001) that is meaningless in an f32
|
|
127
|
-
// and only makes the emitted shader harder to read.
|
|
128
|
-
return fmtFloat(Number(((r * r * 2 * Math.max(l, 0)) / 0.08).toPrecision(9)))
|
|
129
|
-
}
|
|
130
|
-
|
|
131
119
|
const uberNode = (slot: number): NodeSpec => ({
|
|
132
120
|
inputs: {
|
|
133
121
|
base: C([1, 1, 1], true),
|
|
@@ -176,6 +164,37 @@ const faceSdfNode = (slot: number): NodeSpec => ({
|
|
|
176
164
|
})
|
|
177
165
|
const FACE_SDF_NODES: Record<string, NodeSpec> = Object.fromEntries([0, 1, 2, 3].map((s) => [`ag_face_sdf/${s}`, faceSdfNode(s)]))
|
|
178
166
|
|
|
167
|
+
// A packed property map on the slot the type names — the game's material
|
|
168
|
+
// contract: R metal, G perceptual roughness (answered as smoothness, Unity's
|
|
169
|
+
// word for 1 − r), B occlusion, A emission mask, each remapped through the
|
|
170
|
+
// material's own min/max (see rz_property_map). The four
|
|
171
|
+
// plain values are what the surface is WITHOUT a map, so one look serves a
|
|
172
|
+
// converted set that brought maps and a hand-made stage that brought none.
|
|
173
|
+
// Defaults are the game's: the full 0–1 range, and the glow off (min = max = 1).
|
|
174
|
+
const propertyMapNode = (slot: number): NodeSpec => ({
|
|
175
|
+
inputs: {
|
|
176
|
+
uv: { type: "vector", contextDefault: "vec3f(input.uv, 0.0)" },
|
|
177
|
+
metallic: F(0),
|
|
178
|
+
smoothness: F(0.5),
|
|
179
|
+
occlusion: F(1),
|
|
180
|
+
emission: F(0),
|
|
181
|
+
metal_min: F(0),
|
|
182
|
+
metal_max: F(1),
|
|
183
|
+
rough_min: F(0),
|
|
184
|
+
rough_max: F(1),
|
|
185
|
+
ao_min: F(0),
|
|
186
|
+
ao_max: F(1),
|
|
187
|
+
emission_min: F(1),
|
|
188
|
+
emission_max: F(1),
|
|
189
|
+
},
|
|
190
|
+
outputs: { metallic: "float", smoothness: "float", occlusion: "float", emission: "float" },
|
|
191
|
+
outputSelect: { metallic: ".x", smoothness: ".y", occlusion: ".z", emission: ".w" },
|
|
192
|
+
emit: (a) =>
|
|
193
|
+
`rz_property_map(group_tex${slot}(${a.uv}.xy), rz_group_bound${slot}(), vec4f(${a.metallic}, ${a.smoothness}, ${a.occlusion}, ${a.emission}), ` +
|
|
194
|
+
`vec4f(${a.metal_min}, ${a.rough_min}, ${a.ao_min}, ${a.emission_min}), vec4f(${a.metal_max}, ${a.rough_max}, ${a.ao_max}, ${a.emission_max}))`,
|
|
195
|
+
})
|
|
196
|
+
const PROPERTY_MAP_NODES: Record<string, NodeSpec> = Object.fromEntries([0, 1, 2, 3].map((s) => [`property_map/${s}`, propertyMapNode(s)]))
|
|
197
|
+
|
|
179
198
|
export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
180
199
|
// ── Context inputs (template locals; no emission) ──
|
|
181
200
|
texture: {
|
|
@@ -409,19 +428,43 @@ export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
|
409
428
|
outputs: { color: "color" },
|
|
410
429
|
emit: (a) => `mix(${a.color}, rgb_curve(${a.color}, ${a.y0}, ${a.y1}, ${a.y2}, ${a.y3}, ${a.y4}), ${a.fac})`,
|
|
411
430
|
},
|
|
412
|
-
// UV Map —
|
|
431
|
+
// UV Map — the mesh UV the texture node uses, and the second set: a PMX's
|
|
432
|
+
// second additional UV (xy) where it has one, else the first UV again.
|
|
413
433
|
uv_map: {
|
|
414
434
|
inputs: {},
|
|
415
|
-
outputs: { uv: "vector" },
|
|
416
|
-
contextOutputs: { uv: "vec3f(input.uv, 0.0)" },
|
|
435
|
+
outputs: { uv: "vector", uv2: "vector" },
|
|
436
|
+
contextOutputs: { uv: "vec3f(input.uv, 0.0)", uv2: "vec3f(input.uv2, 0.0)" },
|
|
437
|
+
},
|
|
438
|
+
// Whether each group image slot holds a real map, 1 or 0. A slot nobody
|
|
439
|
+
// filled is a 1×1 white stand-in, and white is not "no map" to a normal or
|
|
440
|
+
// a property map — this is how a look tells the two apart and keeps the
|
|
441
|
+
// surface's own normal where the model brought none.
|
|
442
|
+
image_bound: {
|
|
443
|
+
inputs: {},
|
|
444
|
+
outputs: { slot0: "float", slot1: "float", slot2: "float", slot3: "float" },
|
|
445
|
+
contextOutputs: {
|
|
446
|
+
slot0: "select(0.0, 1.0, rz_group_bound0())",
|
|
447
|
+
slot1: "select(0.0, 1.0, rz_group_bound1())",
|
|
448
|
+
slot2: "select(0.0, 1.0, rz_group_bound2())",
|
|
449
|
+
slot3: "select(0.0, 1.0, rz_group_bound3())",
|
|
450
|
+
},
|
|
417
451
|
},
|
|
418
452
|
|
|
419
453
|
// ── Normal Map, Vector Transform ──
|
|
454
|
+
// Strength scales the map's tilt, so above 1 steepens it (Unity's
|
|
455
|
+
// _NormalScale, Blender's strength); 0 is the surface's own normal.
|
|
420
456
|
normal_map: {
|
|
421
457
|
inputs: { color: C([0.5, 0.5, 1], true), strength: F(1) },
|
|
422
458
|
outputs: { normal: "vector" },
|
|
423
459
|
emit: (a) => `node_normal_map(${a.color}, ${a.strength}, n, input.worldPos, input.uv)`,
|
|
424
460
|
},
|
|
461
|
+
// A normal map packed as game engines store one (DXT5nm/BC5: X in alpha, Y
|
|
462
|
+
// in green, Z rebuilt). Link the image's colour and its alpha.
|
|
463
|
+
"normal_map/packed": {
|
|
464
|
+
inputs: { color: C([1, 0.5, 1], true), alpha: F(0.5), strength: F(1) },
|
|
465
|
+
outputs: { normal: "vector" },
|
|
466
|
+
emit: (a) => `node_normal_map_packed(vec4f(${a.color}, ${a.alpha}), ${a.strength}, n, input.worldPos, input.uv)`,
|
|
467
|
+
},
|
|
425
468
|
"vector_transform/world_to_camera": {
|
|
426
469
|
inputs: { vector: V([0, 0, 0], true) },
|
|
427
470
|
outputs: { vector: "vector" },
|
|
@@ -439,27 +482,20 @@ export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
|
439
482
|
},
|
|
440
483
|
|
|
441
484
|
// ── Shader nodes. Shaders travel as RGB here (see shader_to_rgb_diffuse), so
|
|
442
|
-
// a "transparent" shader is the colour that contributes nothing; the actual
|
|
443
|
-
// cutout is the group's alphaMode, which is where transparency belongs in a
|
|
444
|
-
// rasteriser without order-independent blending.
|
|
445
|
-
bsdf_transparent: { inputs: {}, outputs: { color: "color" }, emit: () => `vec3f(0.0)` },
|
|
446
|
-
bsdf_diffuse: {
|
|
447
|
-
inputs: { color: C([0.8, 0.8, 0.8], true) },
|
|
448
|
-
outputs: { color: "color" },
|
|
449
|
-
emit: (a) => `${a.color} * shader_to_rgb_lit(n, l, sun, amb, shadow)`,
|
|
450
|
-
},
|
|
451
485
|
|
|
452
486
|
// ── Nodes with no meaning on a PMX, answered honestly with a constant ──
|
|
453
487
|
// Each returns what Blender returns for the absent case, so a graph that reads
|
|
454
488
|
// one degrades to a sensible look instead of failing to compile. Documented
|
|
455
489
|
// rather than silently wrong.
|
|
456
490
|
//
|
|
457
|
-
// attribute:
|
|
458
|
-
//
|
|
491
|
+
// attribute: the vertex colour. A PMX carries none of its own, so a model
|
|
492
|
+
// keeps it in its first additional UV (RGBA) — what a converted game stage
|
|
493
|
+
// writes there, a plant's occlusion in the alpha. A model without one reads
|
|
494
|
+
// white and alpha 1, the identity for the multiply these usually feed.
|
|
459
495
|
attribute: {
|
|
460
496
|
inputs: {},
|
|
461
|
-
outputs: { color: "color", fac: "float" },
|
|
462
|
-
contextOutputs: { color: "
|
|
497
|
+
outputs: { color: "color", fac: "float", alpha: "float" },
|
|
498
|
+
contextOutputs: { color: "input.vcolor.rgb", fac: "color_to_value(input.vcolor.rgb)", alpha: "input.vcolor.a" },
|
|
463
499
|
},
|
|
464
500
|
// object_info: one model, one instance — Random is the only field with a real
|
|
465
501
|
// use (per-instance variation) and there are no instances to vary.
|
|
@@ -595,6 +631,66 @@ export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
|
595
631
|
// Defaults are 104903's skin.
|
|
596
632
|
...UBER_NODES,
|
|
597
633
|
...FACE_SDF_NODES,
|
|
634
|
+
...PROPERTY_MAP_NODES,
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Lit — Unity's Lit (URP's PBR), term for term: what a stage, a prop or
|
|
638
|
+
* the weather is shaded with, so a surface authored with Unity's inputs
|
|
639
|
+
* looks here as it does there. See urp_lit in nodes.ts for the formula.
|
|
640
|
+
*
|
|
641
|
+
* smoothness is Unity's (1 − perceptual roughness). emission is added on
|
|
642
|
+
* top, as HDR colour. alpha < 1 is Transparent with Preserve Specular
|
|
643
|
+
* Lighting — the diffuse fades, the reflection does not; the `alpha`
|
|
644
|
+
* output is the coverage to hand the graph's opacity, on a group blending
|
|
645
|
+
* "premultiplied".
|
|
646
|
+
*/
|
|
647
|
+
lit: {
|
|
648
|
+
inputs: {
|
|
649
|
+
base_color: C([0.8, 0.8, 0.8], true),
|
|
650
|
+
metallic: F(0),
|
|
651
|
+
smoothness: F(0.5),
|
|
652
|
+
/** A dielectric's reflectance, as Unreal's and HDRP's Specular: 0.5 is
|
|
653
|
+
* URP's fixed 0.04, 1 is 0.08 — glossy cloth, lacquer, wet skin. */
|
|
654
|
+
specular: F(0.5),
|
|
655
|
+
occlusion: F(1),
|
|
656
|
+
emission: C([0, 0, 0]),
|
|
657
|
+
alpha: F(1),
|
|
658
|
+
normal: { type: "vector", contextDefault: "n" },
|
|
659
|
+
},
|
|
660
|
+
outputs: { color: "color", alpha: "float" },
|
|
661
|
+
outputSelect: { color: ".rgb", alpha: ".a" },
|
|
662
|
+
emit: (a) =>
|
|
663
|
+
`urp_lit(${a.base_color}, ${a.metallic}, ${a.smoothness}, ${a.specular}, ${a.occlusion}, ${a.emission}, ${a.alpha}, ` +
|
|
664
|
+
`${a.normal}, l, v, sun, amb, shadow, input.worldPos, true)`,
|
|
665
|
+
takesLight: true,
|
|
666
|
+
},
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* The scene's reflection along a direction — the stage's own reflection
|
|
670
|
+
* probe, box-projected to the stage's bounds, where the engine captured one;
|
|
671
|
+
* the sky where it did not. Roughness picks the blur on the game's curve.
|
|
672
|
+
* Its default vector is this surface's reflection, so a glass shell or a
|
|
673
|
+
* water surface wires only what it bends.
|
|
674
|
+
*/
|
|
675
|
+
/**
|
|
676
|
+
* The highlights alone: the sun's and the lamps' specular on a GGX lobe
|
|
677
|
+
* (URP's, α = r²), untinted. A water or glass look that computes its own
|
|
678
|
+
* reflection multiplies this by its reflectance for the glints.
|
|
679
|
+
*/
|
|
680
|
+
glossy_direct: {
|
|
681
|
+
inputs: { smoothness: F(0.9), normal: { type: "vector", contextDefault: "n" } },
|
|
682
|
+
outputs: { color: "color" },
|
|
683
|
+
emit: (a) => `rz_glossy_direct(${a.normal}, l, v, sun, shadow, input.worldPos, 1.0 - (${a.smoothness}))`,
|
|
684
|
+
takesLight: true,
|
|
685
|
+
},
|
|
686
|
+
reflection_probe: {
|
|
687
|
+
inputs: {
|
|
688
|
+
vector: { type: "vector", contextDefault: "reflect(-v, n)" },
|
|
689
|
+
smoothness: F(1),
|
|
690
|
+
},
|
|
691
|
+
outputs: { color: "color" },
|
|
692
|
+
emit: (a) => `rzProbeSpecular(${a.vector}, input.worldPos, 1.0 - (${a.smoothness}))`,
|
|
693
|
+
},
|
|
598
694
|
|
|
599
695
|
// ── Blender 5.2 colour utilities ──
|
|
600
696
|
separate_color: {
|
|
@@ -793,26 +889,6 @@ export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
|
793
889
|
outputs: { color: "color" },
|
|
794
890
|
emit: (a) => `${a.a} + vec3f(${a.b})`,
|
|
795
891
|
},
|
|
796
|
-
// Blender Emission shader: color × strength. In the ShaderToRGB-era ports the
|
|
797
|
-
// emission result feeds a Mix Shader directly as radiance. Color may be a literal
|
|
798
|
-
// (body's rim tints) or linked.
|
|
799
|
-
emission: {
|
|
800
|
-
inputs: { color: C([1, 1, 1]), strength: F(1) },
|
|
801
|
-
outputs: { color: "color" },
|
|
802
|
-
emit: (a) => `${a.color} * ${a.strength}`,
|
|
803
|
-
},
|
|
804
|
-
// Blender Add Shader — radiance sum of two evaluated shading results.
|
|
805
|
-
add_shader: {
|
|
806
|
-
inputs: { a: C([0, 0, 0], true), b: C([0, 0, 0], true) },
|
|
807
|
-
outputs: { color: "color" },
|
|
808
|
-
emit: (a) => `${a.a} + ${a.b}`,
|
|
809
|
-
},
|
|
810
|
-
// Mix Shader — plain lerp between two evaluated shading results.
|
|
811
|
-
mix_shader: {
|
|
812
|
-
inputs: { fac: F(0.5), a: C([0, 0, 0], true), b: C([0, 0, 0]) },
|
|
813
|
-
outputs: { color: "color" },
|
|
814
|
-
emit: (a) => `mix(${a.a}, ${a.b}, ${a.fac})`,
|
|
815
|
-
},
|
|
816
892
|
|
|
817
893
|
// ── View-dependent scalars ──
|
|
818
894
|
fresnel: { inputs: { ior: F(1.45) }, outputs: { value: "float" }, emit: (a) => `fresnel(${a.ior}, n, v)` },
|
|
@@ -833,38 +909,19 @@ export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
|
833
909
|
emit: (a) => `layer_weight_facing(${a.blend}, ${a.normal}, v)`,
|
|
834
910
|
},
|
|
835
911
|
|
|
836
|
-
/**
|
|
837
|
-
* The world's radiance from a direction — Blender's Environment Texture, read
|
|
838
|
-
* off the scene's own sky rather than off an image a material carries.
|
|
839
|
-
*
|
|
840
|
-
* ITS DEFAULT VECTOR IS THE REFLECTION, which is the whole point: wire
|
|
841
|
-
* nothing and it answers what this surface reflects. The roughness input
|
|
842
|
-
* picks how blurred the answer is; at 0 it is a mirror.
|
|
843
|
-
*
|
|
844
|
-
* A scene with no HDRI has no sky to reflect, and this returns the flat world
|
|
845
|
-
* colour — the same value the ambient already carries, so such a scene looks
|
|
846
|
-
* exactly as it did.
|
|
847
|
-
*/
|
|
848
|
-
environment: {
|
|
849
|
-
inputs: {
|
|
850
|
-
vector: { type: "vector", contextDefault: "reflect(-v, n)" },
|
|
851
|
-
roughness: F(0),
|
|
852
|
-
},
|
|
853
|
-
outputs: { color: "color" },
|
|
854
|
-
emit: (a) => `rzWorldSpecular(${a.vector}, ${a.roughness})`,
|
|
855
|
-
},
|
|
856
912
|
|
|
857
913
|
// ── Lighting capture ──
|
|
858
914
|
/**
|
|
859
|
-
*
|
|
860
|
-
*
|
|
861
|
-
*
|
|
862
|
-
*
|
|
915
|
+
* Lambert — URP's LightingLambert for the main light, plus the ambient:
|
|
916
|
+
* what a white surface facing `normal` receives. A toon look ramps `value`
|
|
917
|
+
* (its luminance) into a terminator, or tints by `color`. Shader Graph's
|
|
918
|
+
* custom-lighting idiom, in one node.
|
|
863
919
|
*/
|
|
864
|
-
|
|
865
|
-
inputs: {},
|
|
866
|
-
outputs: { value: "float" },
|
|
867
|
-
|
|
920
|
+
lambert: {
|
|
921
|
+
inputs: { normal: { type: "vector", contextDefault: "n" } },
|
|
922
|
+
outputs: { color: "color", value: "float" },
|
|
923
|
+
outputSelect: { color: ".rgb", value: ".a" },
|
|
924
|
+
emit: (a) => `urp_lambert(${a.normal}, l, sun, amb, shadow)`,
|
|
868
925
|
},
|
|
869
926
|
/**
|
|
870
927
|
* Hands this surface to the screen-space scattering pass: the colour passes
|
|
@@ -878,24 +935,6 @@ export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
|
878
935
|
emit: (a) => `rz_subsurface(${a.strength}, ${a.color})`,
|
|
879
936
|
},
|
|
880
937
|
|
|
881
|
-
/**
|
|
882
|
-
* The same closure as a COLOUR, which is what Blender's Shader to RGB
|
|
883
|
-
* actually returns.
|
|
884
|
-
*
|
|
885
|
-
* A warm sun against a cool ambient radiates warm light and cool shadow, and
|
|
886
|
-
* that colour IS the look in most of this style. Reducing it to luminance
|
|
887
|
-
* first — the only thing reachable before — leaves every ramp working on grey.
|
|
888
|
-
*
|
|
889
|
-
* Linking this into a scalar socket still yields a float, because Blender's
|
|
890
|
-
* own implicit Color → Value conversion (BT.601) happens at the link. So the
|
|
891
|
-
* two nodes differ only in whether the graph wants the colour or the scalar,
|
|
892
|
-
* and each gets Blender's answer for its own question.
|
|
893
|
-
*/
|
|
894
|
-
shader_to_rgb: {
|
|
895
|
-
inputs: {},
|
|
896
|
-
outputs: { color: "color" },
|
|
897
|
-
emit: () => `shader_to_rgb_lit(n, l, sun, amb, shadow)`,
|
|
898
|
-
},
|
|
899
938
|
|
|
900
939
|
// ── Vector ──
|
|
901
940
|
separate_xyz: {
|
|
@@ -956,70 +995,6 @@ export const NODE_REGISTRY: Record<string, NodeSpec> = {
|
|
|
956
995
|
// ── Principled BSDF (frozen 3.6 legacy-EEVEE semantics: eval_principled port) ──
|
|
957
996
|
// normal defaults to the template's shading normal; link a bump/normal_map chain
|
|
958
997
|
// to perturb it (body/cloth_rough noise bump).
|
|
959
|
-
/**
|
|
960
|
-
* Principled BSDF, Blender 5.2 socket names.
|
|
961
|
-
*
|
|
962
|
-
* v2 (4.0+) renamed most of what a graph touches and re-derived one of them,
|
|
963
|
-
* which is exactly where a transcribed port goes quietly wrong: Specular
|
|
964
|
-
* became "Specular IOR Level" and no longer sets reflectance directly — IOR
|
|
965
|
-
* does, and the level scales it. So f0 is computed the v2 way here, and the
|
|
966
|
-
* defaults land on 0.04 for ior 1.5 at level 0.5, matching both versions at
|
|
967
|
-
* their defaults while tracking v2 when a preset moves either.
|
|
968
|
-
*
|
|
969
|
-
* Emission Strength defaults to 0 in v2 where 3.6's Emission was visible, so a
|
|
970
|
-
* naive port turns every emissive material black. Following v2 means honouring
|
|
971
|
-
* that default and letting the preset say otherwise.
|
|
972
|
-
*
|
|
973
|
-
* Not implemented, and not silently wrong — a graph setting these gets the
|
|
974
|
-
* base BSDF rather than a wrong approximation of them: Coat, Transmission,
|
|
975
|
-
* Subsurface, Anisotropic, Thin Film. They need path-traced or
|
|
976
|
-
* multi-scatter machinery this renderer does not have.
|
|
977
|
-
*/
|
|
978
|
-
principled: {
|
|
979
|
-
inputs: {
|
|
980
|
-
base_color: C([0.8, 0.8, 0.8], true),
|
|
981
|
-
metallic: F(0),
|
|
982
|
-
roughness: F(0.5),
|
|
983
|
-
ior: F(1.5),
|
|
984
|
-
specular_ior_level: F(0.5),
|
|
985
|
-
sheen_weight: F(0),
|
|
986
|
-
sheen_tint: F(0),
|
|
987
|
-
emission_color: C([1, 1, 1]),
|
|
988
|
-
emission_strength: F(0),
|
|
989
|
-
normal: { type: "vector", contextDefault: "n" },
|
|
990
|
-
/** Ours, not Blender's: caps firefly speculars from noise-bumped NDF
|
|
991
|
-
* aliasing, which EEVEE hides behind TAA and we have none. */
|
|
992
|
-
spec_clamp: F(1e30),
|
|
993
|
-
/** Ours: how roughness picks the reflection's blur. 0 is the engine's
|
|
994
|
-
* sqrt ramp; 1 is Unity's probe curve, p·(1.7−0.7p) mip-steps per
|
|
995
|
-
* eighth, stopping where a cube's last face texel does — what a stage
|
|
996
|
-
* ripped from a Unity game was authored against. See rzWorldSpecularLod. */
|
|
997
|
-
reflection_lod: F(0),
|
|
998
|
-
/** Ours: 1 shades the sun's and the lamps' highlights with URP's
|
|
999
|
-
* direct-light BRDF, as a stage from a Unity game was lit — see bsdf_urp. */
|
|
1000
|
-
unity_direct: F(0),
|
|
1001
|
-
},
|
|
1002
|
-
outputs: { color: "color" },
|
|
1003
|
-
emit: (a) => {
|
|
1004
|
-
// Fold the reflectance when both sockets are literals, which is nearly
|
|
1005
|
-
// always. It keeps the emitted WGSL as tight as the 3.6 form was — and at
|
|
1006
|
-
// the defaults it folds to exactly 0.5, which is what 3.6's Specular held,
|
|
1007
|
-
// so the two versions agree byte-for-byte where a preset changed nothing.
|
|
1008
|
-
const spec = foldSpecular(a.ior, a.specular_ior_level)
|
|
1009
|
-
const bsdf =
|
|
1010
|
-
`eval_principled(PrincipledIn(${a.base_color}, ${a.metallic}, ` +
|
|
1011
|
-
`${spec}, ${a.roughness}, ` +
|
|
1012
|
-
`${a.spec_clamp}, ${a.sheen_weight}, ${a.sheen_tint}, ${a.reflection_lod}, ${a.unity_direct}), ${a.normal}, l, v, sun, amb, shadow, input.worldPos)`
|
|
1013
|
-
// v2 defaults Emission Strength to 0, which is the overwhelming case. Emit
|
|
1014
|
-
// the term only when it can do something, so the common shader carries no
|
|
1015
|
-
// dead add and the output stays readable.
|
|
1016
|
-
const lit = a.emission_strength === "0.0" ? bsdf : `${bsdf} + ${a.emission_color} * ${a.emission_strength}`
|
|
1017
|
-
// Unity mode also ends as SimPipeline's surfaces end: saturate()d, so no
|
|
1018
|
-
// lit or glowing surface passes 1.0 before the post — its bloom works on
|
|
1019
|
-
// what is left under that. A literal, as every stage graph states it.
|
|
1020
|
-
return a.unity_direct === "1.0" ? `min(${lit}, vec3f(1.0))` : lit
|
|
1021
|
-
},
|
|
1022
|
-
},
|
|
1023
998
|
}
|
|
1024
999
|
|
|
1025
1000
|
// ─── Blender socket parity ────────────────────────────────────────────
|
|
@@ -12,8 +12,11 @@ export type RenderClass = "auto" | "eye" | "hair"
|
|
|
12
12
|
export type AlphaMode = "opaque" | "hashed"
|
|
13
13
|
|
|
14
14
|
/** How a group's colour meets the frame. "over" is the ordinary alpha blend;
|
|
15
|
-
* "additive" adds its light and never reads alpha
|
|
16
|
-
|
|
15
|
+
* "additive" adds its light and never reads alpha; "premultiplied" takes a
|
|
16
|
+
* colour already weighted by its own coverage (One, OneMinusSrcAlpha) — a
|
|
17
|
+
* game's glass, whose reflection must not fade with its transparency. See
|
|
18
|
+
* StyleGroup.blend. */
|
|
19
|
+
export type StyleBlend = "over" | "additive" | "premultiplied"
|
|
17
20
|
|
|
18
21
|
/** Descriptive manifest for hosts (reze-design) so the render-class picker is data-driven
|
|
19
22
|
* instead of hardcoding strings. The effect implementations stay engine-side; this only
|
package/src/graph/slots.ts
CHANGED
|
@@ -208,8 +208,12 @@ function epilogue(renderClass: RenderClass, alphaMode: AlphaMode, hasOpacity: bo
|
|
|
208
208
|
const BURN = ` + RZ_BURN_COLOR * rz_burn`
|
|
209
209
|
// The scene fog, last, as the game lays it over a lit surface; an
|
|
210
210
|
// emission-only graph takes none (the game's effect shaders have no fog).
|
|
211
|
+
// A premultiplied colour takes its fog premultiplied too: the haze a pane
|
|
212
|
+
// adds is the haze over its own coverage, the rest arrives through what is
|
|
213
|
+
// behind it (the game scales its fog by the output alpha the same way).
|
|
214
|
+
const fogOver = blend === "premultiplied" ? " * out.color.a" : ""
|
|
211
215
|
const FOG = takesLight
|
|
212
|
-
? ` out.color = vec4f(mix(mix(out.color.rgb, light.fog[0].rgb, input.fog.x), light.fog[2].rgb, input.fog.y), out.color.a);\n`
|
|
216
|
+
? ` out.color = vec4f(mix(mix(out.color.rgb, light.fog[0].rgb${fogOver}, input.fog.x), light.fog[2].rgb${fogOver}, input.fog.y), out.color.a);\n`
|
|
213
217
|
: ""
|
|
214
218
|
if (renderClass === "hair") {
|
|
215
219
|
return `${ALBEDO} var outAlpha = ${alphaBase};
|
package/src/model.ts
CHANGED
|
@@ -566,9 +566,11 @@ export class Model {
|
|
|
566
566
|
rigidbodies: Rigidbody[] = [],
|
|
567
567
|
joints: Joint[] = [],
|
|
568
568
|
loadWarnings: string[] = [],
|
|
569
|
-
edgeScales: Float32Array | null = null
|
|
569
|
+
edgeScales: Float32Array | null = null,
|
|
570
|
+
additionalUvs: Float32Array[] = []
|
|
570
571
|
) {
|
|
571
572
|
this.edgeScales = edgeScales
|
|
573
|
+
this.additionalUvs = additionalUvs
|
|
572
574
|
// Store base vertex data (original positions before morphing)
|
|
573
575
|
this.baseVertexData = new Float32Array(vertexData)
|
|
574
576
|
this.vertexData = vertexData
|
|
@@ -1006,6 +1008,14 @@ export class Model {
|
|
|
1006
1008
|
|
|
1007
1009
|
/** PMX per-vertex edge scale, when the source had one. */
|
|
1008
1010
|
private edgeScales: Float32Array | null = null
|
|
1011
|
+
private additionalUvs: Float32Array[]
|
|
1012
|
+
|
|
1013
|
+
/** A PMX additional UV channel (1–4) as loaded, four floats a vertex — or
|
|
1014
|
+
* null when the file has fewer. Its morphs (types 4–7) are in getMorphing(),
|
|
1015
|
+
* with uvOffsets; the engine applies neither, a native look reads both. */
|
|
1016
|
+
getAdditionalUv(channel: number): Float32Array | null {
|
|
1017
|
+
return this.additionalUvs[channel - 1] ?? null
|
|
1018
|
+
}
|
|
1009
1019
|
private outlineVertices: Float32Array<ArrayBuffer> | null = null
|
|
1010
1020
|
|
|
1011
1021
|
/**
|
|
@@ -1132,6 +1142,28 @@ export class Model {
|
|
|
1132
1142
|
return this.runtimeSkeleton.worldMatrices[idx].getPosition()
|
|
1133
1143
|
}
|
|
1134
1144
|
|
|
1145
|
+
/**
|
|
1146
|
+
* The point `distance` model units down a bone's own axis, posed — where
|
|
1147
|
+
* `#anchor <bone> along <d>` puts an effect. The axis is the bone's rest tail
|
|
1148
|
+
* direction (the way a PMX editor draws it pointing) turned by the bone's
|
|
1149
|
+
* pose, the same mapping the named-points tips use. Model space, like
|
|
1150
|
+
* getBoneWorldPosition. A bone with no tail has no axis: the joint itself.
|
|
1151
|
+
*/
|
|
1152
|
+
getBonePointAlong(boneName: string, distance: number): Vec3 | null {
|
|
1153
|
+
const idx = this.runtimeSkeleton.nameIndex[boneName]
|
|
1154
|
+
if (idx === undefined || idx < 0) return null
|
|
1155
|
+
const w = this.runtimeSkeleton.worldMatrices[idx].values
|
|
1156
|
+
const t = this.skeleton.bones[idx]?.tail
|
|
1157
|
+
const len = t ? Math.hypot(t[0], t[1], t[2]) : 0
|
|
1158
|
+
if (!t || len < 1e-6) return new Vec3(w[12], w[13], w[14])
|
|
1159
|
+
const k = distance / len
|
|
1160
|
+
return new Vec3(
|
|
1161
|
+
w[12] + (w[0] * t[0] + w[4] * t[1] + w[8] * t[2]) * k,
|
|
1162
|
+
w[13] + (w[1] * t[0] + w[5] * t[1] + w[9] * t[2]) * k,
|
|
1163
|
+
w[14] + (w[2] * t[0] + w[6] * t[1] + w[10] * t[2]) * k,
|
|
1164
|
+
)
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1135
1167
|
/**
|
|
1136
1168
|
* A bone's forward axis, normalised — which way a foot points, where a head
|
|
1137
1169
|
* looks. Model space, like getBoneWorldPosition: the caller composes the model
|
package/src/pmx-loader.ts
CHANGED
|
@@ -36,6 +36,10 @@ export class PmxLoader {
|
|
|
36
36
|
private inverseBindMatrices: Float32Array | null = null
|
|
37
37
|
private joints0: Uint16Array | null = null
|
|
38
38
|
private weights0: Uint8Array | null = null
|
|
39
|
+
/** The PMX's additional UV channels (1–4), each four floats a vertex. The
|
|
40
|
+
* engine's own shading reads none of them; a native look can name one as
|
|
41
|
+
* the carrier of a game vertex stream (NativeLook.streams). */
|
|
42
|
+
private additionalUvs: Float32Array[] = []
|
|
39
43
|
/** PMX per-vertex edge scale — the outline's width multiplier, as the
|
|
40
44
|
* game's outline takes vertex colour alpha. */
|
|
41
45
|
private edgeScales: Float32Array | null = null
|
|
@@ -154,6 +158,7 @@ export class PmxLoader {
|
|
|
154
158
|
const joints = new Uint16Array(count * 4)
|
|
155
159
|
const weights = new Uint8Array(count * 4) // UNORM8, will be normalized to 255
|
|
156
160
|
const edgeScales = new Float32Array(count)
|
|
161
|
+
this.additionalUvs = Array.from({ length: this.additionalVec4Count }, () => new Float32Array(count * 4))
|
|
157
162
|
|
|
158
163
|
for (let i = 0; i < count; i++) {
|
|
159
164
|
const px = this.getFloat32()
|
|
@@ -171,7 +176,10 @@ export class PmxLoader {
|
|
|
171
176
|
// PMX UVs are in the same orientation as WebGPU sampling; no flip
|
|
172
177
|
uvs.push(u, v)
|
|
173
178
|
|
|
174
|
-
|
|
179
|
+
for (let k = 0; k < this.additionalVec4Count; k++) {
|
|
180
|
+
const a = this.additionalUvs[k]
|
|
181
|
+
for (let c = 0; c < 4; c++) a[i * 4 + c] = this.getFloat32()
|
|
182
|
+
}
|
|
175
183
|
const type = this.getUint8()
|
|
176
184
|
const base = i * 4
|
|
177
185
|
// Initialize defaults
|
|
@@ -1142,7 +1150,8 @@ export class PmxLoader {
|
|
|
1142
1150
|
this.rigidbodies,
|
|
1143
1151
|
this.joints,
|
|
1144
1152
|
this.warnings,
|
|
1145
|
-
this.edgeScales
|
|
1153
|
+
this.edgeScales,
|
|
1154
|
+
this.additionalUvs
|
|
1146
1155
|
)
|
|
1147
1156
|
}
|
|
1148
1157
|
|
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
interface AnchorRequest {
|
|
24
24
|
bone: string
|
|
25
25
|
trail: boolean
|
|
26
|
+
/** Model units down the bone's own axis (`#anchor ... along d`); absent = 0,
|
|
27
|
+
* the joint itself. */
|
|
28
|
+
along?: number
|
|
29
|
+
/** World units the bone must move before its trail keeps a point
|
|
30
|
+
* (`trail step d`); absent = every 60 Hz tick. */
|
|
31
|
+
step?: number
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
/** The empty table — a scene with no effect installed asks for no bones. */
|
|
@@ -48,6 +54,11 @@ export interface AnchorTable {
|
|
|
48
54
|
* Order is first-come, so a single effect gets the identity alias and the whole
|
|
49
55
|
* mechanism is a no-op until a second effect exists — which is what makes this
|
|
50
56
|
* safe to land before setEffects does.
|
|
57
|
+
*
|
|
58
|
+
* A point ALONG a bone is a different point: `#anchor 右手首 trail along 0.9`
|
|
59
|
+
* and a bare `#anchor 右手首` are two entries, keyed by bone and distance, since
|
|
60
|
+
* sharing one ring would hand one of them the other's path. So is a trail
|
|
61
|
+
* recorded at a different `step`: its ring keeps different points.
|
|
51
62
|
*/
|
|
52
63
|
export function buildAnchorTable(requests: AnchorRequest[][], max: number): AnchorTable {
|
|
53
64
|
const entries: AnchorRequest[] = []
|
|
@@ -58,7 +69,8 @@ export function buildAnchorTable(requests: AnchorRequest[][], max: number): Anch
|
|
|
58
69
|
for (let e = 0; e < requests.length; e++) {
|
|
59
70
|
const local: number[] = []
|
|
60
71
|
for (const req of requests[e]) {
|
|
61
|
-
|
|
72
|
+
const key = req.along || req.step ? `${req.bone}\u0000${req.along ?? 0}\u0000${req.step ?? 0}` : req.bone
|
|
73
|
+
let g = index.get(key)
|
|
62
74
|
if (g === undefined) {
|
|
63
75
|
if (entries.length >= max) {
|
|
64
76
|
// Refused, and the effect still installs: an effect that loses one of
|
|
@@ -69,8 +81,8 @@ export function buildAnchorTable(requests: AnchorRequest[][], max: number): Anch
|
|
|
69
81
|
continue
|
|
70
82
|
}
|
|
71
83
|
g = entries.length
|
|
72
|
-
index.set(
|
|
73
|
-
entries.push({ bone: req.bone, trail: req.trail })
|
|
84
|
+
index.set(key, g)
|
|
85
|
+
entries.push({ bone: req.bone, trail: req.trail, ...(req.along ? { along: req.along } : {}), ...(req.step ? { step: req.step } : {}) })
|
|
74
86
|
} else if (req.trail) {
|
|
75
87
|
// A later request for the same bone can only ever ADD the trail — the
|
|
76
88
|
// ring is shared, and turning it on for one reader turns it on for all.
|