reze-engine 0.17.1 → 0.18.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.
Files changed (65) hide show
  1. package/dist/engine.d.ts +34 -0
  2. package/dist/engine.d.ts.map +1 -1
  3. package/dist/engine.js +258 -15
  4. package/dist/gpu-profile.d.ts +19 -0
  5. package/dist/gpu-profile.d.ts.map +1 -0
  6. package/dist/gpu-profile.js +120 -0
  7. package/dist/graph/compile.d.ts +34 -0
  8. package/dist/graph/compile.d.ts.map +1 -0
  9. package/dist/graph/compile.js +299 -0
  10. package/dist/graph/presets/body.d.ts +3 -0
  11. package/dist/graph/presets/body.d.ts.map +1 -0
  12. package/dist/graph/presets/body.js +100 -0
  13. package/dist/graph/presets/cloth_rough.d.ts +3 -0
  14. package/dist/graph/presets/cloth_rough.d.ts.map +1 -0
  15. package/dist/graph/presets/cloth_rough.js +61 -0
  16. package/dist/graph/presets/cloth_smooth.d.ts +3 -0
  17. package/dist/graph/presets/cloth_smooth.d.ts.map +1 -0
  18. package/dist/graph/presets/cloth_smooth.js +53 -0
  19. package/dist/graph/presets/default.d.ts +3 -0
  20. package/dist/graph/presets/default.d.ts.map +1 -0
  21. package/dist/graph/presets/default.js +20 -0
  22. package/dist/graph/presets/hair.d.ts +3 -0
  23. package/dist/graph/presets/hair.d.ts.map +1 -0
  24. package/dist/graph/presets/hair.js +79 -0
  25. package/dist/graph/presets/metal.d.ts +3 -0
  26. package/dist/graph/presets/metal.d.ts.map +1 -0
  27. package/dist/graph/presets/metal.js +58 -0
  28. package/dist/graph/presets/stockings.d.ts +3 -0
  29. package/dist/graph/presets/stockings.d.ts.map +1 -0
  30. package/dist/graph/presets/stockings.js +54 -0
  31. package/dist/graph/registry.d.ts +28 -0
  32. package/dist/graph/registry.d.ts.map +1 -0
  33. package/dist/graph/registry.js +322 -0
  34. package/dist/graph/schema.d.ts +66 -0
  35. package/dist/graph/schema.d.ts.map +1 -0
  36. package/dist/graph/schema.js +6 -0
  37. package/dist/graph/slots.d.ts +14 -0
  38. package/dist/graph/slots.d.ts.map +1 -0
  39. package/dist/graph/slots.js +123 -0
  40. package/dist/index.d.ts +11 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +9 -0
  43. package/dist/physics/profile.d.ts +18 -0
  44. package/dist/physics/profile.d.ts.map +1 -0
  45. package/dist/physics/profile.js +44 -0
  46. package/package.json +2 -1
  47. package/src/engine.ts +290 -15
  48. package/src/graph/compile.ts +342 -0
  49. package/src/graph/presets/body.ts +103 -0
  50. package/src/graph/presets/cloth_rough.ts +64 -0
  51. package/src/graph/presets/cloth_smooth.ts +56 -0
  52. package/src/graph/presets/default.ts +23 -0
  53. package/src/graph/presets/hair.ts +82 -0
  54. package/src/graph/presets/metal.ts +61 -0
  55. package/src/graph/presets/stockings.ts +57 -0
  56. package/src/graph/registry.ts +351 -0
  57. package/src/graph/schema.ts +60 -0
  58. package/src/graph/slots.ts +145 -0
  59. package/src/index.ts +25 -0
  60. package/dist/physics-debug.d.ts +0 -30
  61. package/dist/physics-debug.d.ts.map +0 -1
  62. package/dist/physics-debug.js +0 -526
  63. package/dist/shaders/passes/physics-debug.d.ts +0 -2
  64. package/dist/shaders/passes/physics-debug.d.ts.map +0 -1
  65. package/dist/shaders/passes/physics-debug.js +0 -69
@@ -0,0 +1,79 @@
1
+ // M_Hair — 仿深空之眼渲染预设v1.0_by_小绿毛猫, as a StyleGraph. Node ids mirror the
2
+ // Blender node names referenced in shaders/materials/hair.ts comments; compiled with
3
+ // { inlineParams: true } this graph must reproduce HAIR_SHADER_WGSL's fragment body
4
+ // (golden test #1 — see tests/graph.test.mjs).
5
+ //
6
+ // Inherited authoring decisions from the hand port: the noise→bump subtree on
7
+ // Principled.Normal is omitted (imperceptible at 0.2 mix weight), and Blender's bevel
8
+ // node is approximated by saturate(normal.y) — Blender Z-up ⇒ engine Y-up.
9
+ //
10
+ // The object is pure JSON (no functions/undefined) — JSON.stringify round-trips it,
11
+ // which is how reze.design will ship additional presets.
12
+ export const HAIR_GRAPH = {
13
+ version: 1,
14
+ name: "Hair",
15
+ slot: "hair",
16
+ nodes: [
17
+ { id: "tex", type: "texture" },
18
+ { id: "geo", type: "geometry" },
19
+ { id: "hs_shadow", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.2, value: 0.5, fac: 1.0 } },
20
+ { id: "hs_002", type: "hue_sat", inputs: { hue: 0.48, saturation: 1.2, value: 0.7, fac: 1.0 } },
21
+ { id: "hs_001", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.5, value: 1.0, fac: 1.0 } },
22
+ { id: "str", type: "shader_to_rgb_diffuse" },
23
+ { id: "ramp_008", type: "ramp_constant", inputs: { pos0: 0.0, color0: [0, 0, 0, 1], pos1: 0.2966, color1: [1, 1, 1, 1] } },
24
+ { id: "mix_004", type: "mix/blend" },
25
+ { id: "bc", type: "bright_contrast", inputs: { bright: 0.1, contrast: 0.2 } },
26
+ { id: "sep_n", type: "separate_xyz" },
27
+ { id: "bevel_clamp", type: "math/clamp01" },
28
+ { id: "mix_003", type: "mix/blend" },
29
+ { id: "fres", type: "fresnel", inputs: { ior: 1.45 } },
30
+ { id: "lw", type: "layer_weight/fresnel", inputs: { blend: 0.61 } },
31
+ { id: "rim_mul", type: "math/multiply" },
32
+ { id: "rim_pow", type: "math/power", inputs: { b: 0.6300000548362732 } },
33
+ {
34
+ id: "mix_shader_002",
35
+ type: "mix_shader",
36
+ inputs: { b: [0.1673291176557541, 0.1673291176557541, 0.1673291176557541] },
37
+ },
38
+ { id: "gate", type: "math/greater_than", inputs: { b: 0.15000000596046448 } },
39
+ { id: "gate_scale", type: "math/multiply", inputs: { b: 0.1 } },
40
+ { id: "npr_add", type: "mix/add_emit" },
41
+ {
42
+ id: "principled",
43
+ type: "principled",
44
+ inputs: { metallic: 0.0, specular: 1.0, roughness: 0.3, spec_clamp: 10.0, sheen: 0.0, sheen_tint: 0.0 },
45
+ },
46
+ { id: "mix_shader_001", type: "mix_shader", inputs: { fac: 0.2 } },
47
+ ],
48
+ links: [
49
+ { from: { node: "tex", socket: "color" }, to: { node: "hs_shadow", socket: "color" } },
50
+ { from: { node: "hs_shadow", socket: "color" }, to: { node: "hs_002", socket: "color" } },
51
+ { from: { node: "tex", socket: "color" }, to: { node: "hs_001", socket: "color" } },
52
+ { from: { node: "str", socket: "value" }, to: { node: "ramp_008", socket: "fac" } },
53
+ { from: { node: "ramp_008", socket: "fac_out" }, to: { node: "mix_004", socket: "fac" } },
54
+ { from: { node: "hs_002", socket: "color" }, to: { node: "mix_004", socket: "a" } },
55
+ { from: { node: "hs_001", socket: "color" }, to: { node: "mix_004", socket: "b" } },
56
+ { from: { node: "mix_004", socket: "color" }, to: { node: "bc", socket: "color" } },
57
+ { from: { node: "geo", socket: "normal" }, to: { node: "sep_n", socket: "vector" } },
58
+ { from: { node: "sep_n", socket: "y" }, to: { node: "bevel_clamp", socket: "a" } },
59
+ { from: { node: "bevel_clamp", socket: "value" }, to: { node: "mix_003", socket: "fac" } },
60
+ { from: { node: "bc", socket: "color" }, to: { node: "mix_003", socket: "a" } },
61
+ { from: { node: "hs_002", socket: "color" }, to: { node: "mix_003", socket: "b" } },
62
+ { from: { node: "fres", socket: "value" }, to: { node: "rim_mul", socket: "a" } },
63
+ { from: { node: "lw", socket: "value" }, to: { node: "rim_mul", socket: "b" } },
64
+ { from: { node: "rim_mul", socket: "value" }, to: { node: "rim_pow", socket: "a" } },
65
+ { from: { node: "mix_003", socket: "color" }, to: { node: "mix_shader_002", socket: "a" } },
66
+ { from: { node: "rim_pow", socket: "value" }, to: { node: "mix_shader_002", socket: "fac" } },
67
+ { from: { node: "tex", socket: "color" }, to: { node: "gate", socket: "a" } },
68
+ { from: { node: "gate", socket: "value" }, to: { node: "gate_scale", socket: "a" } },
69
+ { from: { node: "mix_shader_002", socket: "color" }, to: { node: "npr_add", socket: "a" } },
70
+ { from: { node: "gate_scale", socket: "value" }, to: { node: "npr_add", socket: "b" } },
71
+ { from: { node: "bc", socket: "color" }, to: { node: "principled", socket: "base" } },
72
+ { from: { node: "npr_add", socket: "color" }, to: { node: "mix_shader_001", socket: "a" } },
73
+ { from: { node: "principled", socket: "color" }, to: { node: "mix_shader_001", socket: "b" } },
74
+ ],
75
+ output: { node: "mix_shader_001", socket: "color" },
76
+ // Adjust-tier params deliberately absent: which sockets deserve sliders (and what
77
+ // they're called) is preset-author curation done in reze.design, not engine data.
78
+ // The exposed-param mechanism itself is covered by tests/graph.test.mjs.
79
+ };
@@ -0,0 +1,3 @@
1
+ import type { StyleGraph } from "../schema";
2
+ export declare const METAL_GRAPH: StyleGraph;
3
+ //# sourceMappingURL=metal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metal.d.ts","sourceRoot":"","sources":["../../../src/graph/presets/metal.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,eAAO,MAAM,WAAW,EAAE,UAoDzB,CAAA"}
@@ -0,0 +1,58 @@
1
+ // M_Metal as a StyleGraph — port of shaders/materials/metal.ts.
2
+ // Metallic Principled (Metallic=1, Specular=1, Roughness=0.3) with a
3
+ // reflection-coord voronoi driving base color (metallic sparkle), plus an NPR
4
+ // toon/overlay emission stack mixed at Fac=0.6967. The voronoi Color→ramp Fac
5
+ // link goes through Blender's implicit BT.601 conversion (color_to_value).
6
+ export const METAL_GRAPH = {
7
+ version: 1,
8
+ name: "Metal",
9
+ slot: "metal",
10
+ nodes: [
11
+ { id: "tex", type: "texture" },
12
+ { id: "geo", type: "geometry" },
13
+ { id: "tex_tint", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 0.800000011920929, fac: 1.0 } },
14
+ { id: "str", type: "shader_to_rgb_diffuse" },
15
+ { id: "ramp_008", type: "ramp_constant_aa", inputs: { edge: 0.2966 } },
16
+ { id: "mix04_fac", type: "math/multiply", inputs: { b: 0.5 } },
17
+ { id: "dark_tex", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 0.19999998807907104, fac: 1.0 } },
18
+ { id: "mix_004", type: "mix/blend" },
19
+ { id: "hue_004", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 2.0, fac: 1.0 } },
20
+ { id: "npr_overlay", type: "mix/overlay", inputs: { fac: 1.0 } },
21
+ { id: "npr_emit", type: "emission", inputs: { strength: 8.100000381469727 } },
22
+ { id: "voro_cross", type: "vect_cross", inputs: { b: [0, 1, 0] } },
23
+ { id: "voro", type: "tex_voronoi/color", inputs: { scale: 4.3 } },
24
+ { id: "voro_ramp", type: "ramp_linear", inputs: { pos0: 0.0, pos1: 1.0 } },
25
+ { id: "hue_006", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.5, value: 1.2999999523162842, fac: 1.0 } },
26
+ { id: "albedo", type: "mix/blend" },
27
+ {
28
+ id: "principled",
29
+ type: "principled",
30
+ inputs: { metallic: 1.0, specular: 1.0, roughness: 0.3, spec_clamp: 1e30, sheen: 0.0, sheen_tint: 0.0 },
31
+ },
32
+ { id: "mix_shader_001", type: "mix_shader", inputs: { fac: 0.6967 } },
33
+ ],
34
+ links: [
35
+ { from: { node: "tex", socket: "color" }, to: { node: "tex_tint", socket: "color" } },
36
+ { from: { node: "str", socket: "value" }, to: { node: "ramp_008", socket: "fac" } },
37
+ { from: { node: "ramp_008", socket: "fac_out" }, to: { node: "mix04_fac", socket: "a" } },
38
+ { from: { node: "tex_tint", socket: "color" }, to: { node: "dark_tex", socket: "color" } },
39
+ { from: { node: "mix04_fac", socket: "value" }, to: { node: "mix_004", socket: "fac" } },
40
+ { from: { node: "dark_tex", socket: "color" }, to: { node: "mix_004", socket: "a" } },
41
+ { from: { node: "tex_tint", socket: "color" }, to: { node: "mix_004", socket: "b" } },
42
+ { from: { node: "mix_004", socket: "color" }, to: { node: "hue_004", socket: "color" } },
43
+ { from: { node: "mix_004", socket: "color" }, to: { node: "npr_overlay", socket: "a" } },
44
+ { from: { node: "hue_004", socket: "color" }, to: { node: "npr_overlay", socket: "b" } },
45
+ { from: { node: "npr_overlay", socket: "color" }, to: { node: "npr_emit", socket: "color" } },
46
+ { from: { node: "geo", socket: "reflection" }, to: { node: "voro_cross", socket: "a" } },
47
+ { from: { node: "voro_cross", socket: "vector" }, to: { node: "voro", socket: "vector" } },
48
+ { from: { node: "voro", socket: "color" }, to: { node: "voro_ramp", socket: "fac" } },
49
+ { from: { node: "tex_tint", socket: "color" }, to: { node: "hue_006", socket: "color" } },
50
+ { from: { node: "voro_ramp", socket: "fac_out" }, to: { node: "albedo", socket: "fac" } },
51
+ { from: { node: "voro_ramp", socket: "fac_out" }, to: { node: "albedo", socket: "a" } },
52
+ { from: { node: "hue_006", socket: "color" }, to: { node: "albedo", socket: "b" } },
53
+ { from: { node: "albedo", socket: "color" }, to: { node: "principled", socket: "base" } },
54
+ { from: { node: "npr_emit", socket: "color" }, to: { node: "mix_shader_001", socket: "a" } },
55
+ { from: { node: "principled", socket: "color" }, to: { node: "mix_shader_001", socket: "b" } },
56
+ ],
57
+ output: { node: "mix_shader_001", socket: "color" },
58
+ };
@@ -0,0 +1,3 @@
1
+ import type { StyleGraph } from "../schema";
2
+ export declare const STOCKINGS_GRAPH: StyleGraph;
3
+ //# sourceMappingURL=stockings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stockings.d.ts","sourceRoot":"","sources":["../../../src/graph/presets/stockings.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAE3C,eAAO,MAAM,eAAe,EAAE,UA8C7B,CAAA"}
@@ -0,0 +1,54 @@
1
+ // M_Stockings as a StyleGraph — port of shaders/materials/stockings.ts.
2
+ // A bbox-gradient × facing-rim mask drives a Mix Shader between an HSV-boosted
3
+ // emission (val 5×) and a sheen Principled. The hashed-alpha discard and the
4
+ // alpha=1 output are slot-owned (see STOCKINGS_TEMPLATE in slots.ts) — the graph
5
+ // computes only the radiance. Blender's Generated coord is approximated with UV,
6
+ // as in the hand port. The grayscale mask feeds Mix Shader Fac through Blender's
7
+ // implicit BT.601 color→float conversion (equal-component vector → same scalar).
8
+ export const STOCKINGS_GRAPH = {
9
+ version: 1,
10
+ name: "Stockings",
11
+ slot: "stockings",
12
+ nodes: [
13
+ { id: "tex", type: "texture" },
14
+ { id: "geo", type: "geometry" },
15
+ { id: "map", type: "mapping", inputs: { loc: [1.0, 1.0, 1.0], rot: [0.0, 1.5708, 1.5708] } },
16
+ { id: "grad", type: "tex_gradient" },
17
+ { id: "ramp_001", type: "ramp_tri" },
18
+ { id: "ramp_002", type: "ramp_cardinal", inputs: { pos0: 0.0, pos1: 0.9565 } },
19
+ { id: "facing", type: "layer_weight/facing", inputs: { blend: 0.4 } },
20
+ { id: "ramp_face", type: "ramp_cardinal", inputs: { pos0: 0.0, pos1: 0.5435 } },
21
+ { id: "mix_001", type: "mix/blend", inputs: { fac: 0.5, a: [1.0, 1.0, 1.0] } },
22
+ { id: "mask", type: "mix/lighten", inputs: { fac: 0.5 } },
23
+ { id: "emission_hs", type: "hue_sat", inputs: { hue: 0.5, saturation: 1.0, value: 5.0, fac: 1.0 } },
24
+ {
25
+ id: "principled",
26
+ type: "principled",
27
+ inputs: {
28
+ metallic: 0.1,
29
+ specular: 1.0,
30
+ roughness: 0.5,
31
+ spec_clamp: 1e30,
32
+ sheen: 0.7017999887466431,
33
+ sheen_tint: 0.5,
34
+ },
35
+ },
36
+ { id: "mix_shader_001", type: "mix_shader" },
37
+ ],
38
+ links: [
39
+ { from: { node: "geo", socket: "uv" }, to: { node: "map", socket: "vector" } },
40
+ { from: { node: "map", socket: "vector" }, to: { node: "grad", socket: "vector" } },
41
+ { from: { node: "grad", socket: "value" }, to: { node: "ramp_001", socket: "fac" } },
42
+ { from: { node: "ramp_001", socket: "value" }, to: { node: "ramp_002", socket: "fac" } },
43
+ { from: { node: "facing", socket: "value" }, to: { node: "ramp_face", socket: "fac" } },
44
+ { from: { node: "ramp_face", socket: "fac_out" }, to: { node: "mix_001", socket: "b" } },
45
+ { from: { node: "mix_001", socket: "color" }, to: { node: "mask", socket: "a" } },
46
+ { from: { node: "ramp_002", socket: "fac_out" }, to: { node: "mask", socket: "b" } },
47
+ { from: { node: "tex", socket: "color" }, to: { node: "emission_hs", socket: "color" } },
48
+ { from: { node: "tex", socket: "color" }, to: { node: "principled", socket: "base" } },
49
+ { from: { node: "emission_hs", socket: "color" }, to: { node: "mix_shader_001", socket: "a" } },
50
+ { from: { node: "principled", socket: "color" }, to: { node: "mix_shader_001", socket: "b" } },
51
+ { from: { node: "mask", socket: "color" }, to: { node: "mix_shader_001", socket: "fac" } },
52
+ ],
53
+ output: { node: "mix_shader_001", socket: "color" },
54
+ };
@@ -0,0 +1,28 @@
1
+ import type { SocketValue } from "./schema";
2
+ export type SockT = "float" | "color" | "vector" | "vec4";
3
+ export type InputSpec = {
4
+ type: SockT;
5
+ default?: SocketValue;
6
+ /** Socket is meaningless as a literal (e.g. the color being processed) — must be linked. */
7
+ requiresLink?: boolean;
8
+ /** Unlinked fallback is a template local, not a literal (e.g. principled.normal → n). */
9
+ contextDefault?: string;
10
+ };
11
+ export type NodeSpec = {
12
+ inputs: Record<string, InputSpec>;
13
+ outputs: Record<string, SockT>;
14
+ /** RHS expression for this node's `let`, from resolved arg expressions keyed by socket. */
15
+ emit?: (args: Record<string, string>) => string;
16
+ /** Context nodes tap template locals directly — no `let` emitted. */
17
+ contextOutputs?: Record<string, string>;
18
+ /** Swizzle applied to the node's variable per output socket (default: none). */
19
+ outputSelect?: Record<string, string>;
20
+ };
21
+ export declare function fmtFloat(x: number): string;
22
+ export declare function fmtValue(value: SocketValue, type: SockT): string;
23
+ /** Does a literal's shape fit a socket type? (Scalar splats onto color/vector/vec4.) */
24
+ export declare function literalFits(value: SocketValue, type: SockT): boolean;
25
+ export declare function canConvert(from: SockT, to: SockT): boolean;
26
+ export declare function convert(from: SockT, to: SockT, expr: string): string;
27
+ export declare const NODE_REGISTRY: Record<string, NodeSpec>;
28
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/graph/registry.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AAE3C,MAAM,MAAM,KAAK,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEzD,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,KAAK,CAAA;IACX,OAAO,CAAC,EAAE,WAAW,CAAA;IACrB,4FAA4F;IAC5F,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IACjC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;IAC9B,2FAA2F;IAC3F,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,MAAM,CAAA;IAC/C,qEAAqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,gFAAgF;IAChF,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACtC,CAAA;AAMD,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI1C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,GAAG,MAAM,CAgBhE;AAED,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,GAAG,OAAO,CAIpE;AAMD,wBAAgB,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAM1D;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAKpE;AA6BD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CA+OlD,CAAA"}
@@ -0,0 +1,322 @@
1
+ // Node registry: one entry per Blender-equivalent node backed by NODES_WGSL (nodes.ts).
2
+ // The registry adds no WGSL of its own — a node type exists here only if its function
3
+ // already exists (validated against EEVEE) in the shader library, or is a WGSL builtin.
4
+ // Semantics are frozen Blender 3.6 legacy-EEVEE; enum modes (math op, mix blend type,
5
+ // ramp interpolation) are part of the type string so they are unambiguously topology.
6
+ // ─── Literal formatting ───────────────────────────────────────────────
7
+ // Deterministic: same graph JSON → byte-identical WGSL. String(x) is JS shortest
8
+ // round-trip, so full-precision Blender constants (0.15000000596046448) survive.
9
+ export function fmtFloat(x) {
10
+ if (!Number.isFinite(x))
11
+ throw new Error(`non-finite literal: ${x}`);
12
+ const s = String(x);
13
+ return /[.e]/.test(s) ? s : s + ".0";
14
+ }
15
+ export function fmtValue(value, type) {
16
+ if (typeof value === "number") {
17
+ if (type === "float")
18
+ return fmtFloat(value);
19
+ if (type === "color" || type === "vector")
20
+ return `vec3f(${fmtFloat(value)})`;
21
+ return `vec4f(vec3f(${fmtFloat(value)}), 1.0)`;
22
+ }
23
+ if (value.length === 3) {
24
+ const [x, y, z] = value;
25
+ if (type === "vec4")
26
+ return `vec4f(${fmtFloat(x)}, ${fmtFloat(y)}, ${fmtFloat(z)}, 1.0)`;
27
+ if (type === "float")
28
+ throw new Error(`vector literal on float socket`);
29
+ // All-equal shorthand matches the hand-written shaders (vec3f(0.167…)).
30
+ if (x === y && y === z)
31
+ return `vec3f(${fmtFloat(x)})`;
32
+ return `vec3f(${fmtFloat(x)}, ${fmtFloat(y)}, ${fmtFloat(z)})`;
33
+ }
34
+ if (type !== "vec4")
35
+ throw new Error(`vec4 literal on ${type} socket`);
36
+ return `vec4f(${value.map(fmtFloat).join(", ")})`;
37
+ }
38
+ /** Does a literal's shape fit a socket type? (Scalar splats onto color/vector/vec4.) */
39
+ export function literalFits(value, type) {
40
+ if (typeof value === "number")
41
+ return true;
42
+ if (value.length === 3)
43
+ return type !== "float";
44
+ return type === "vec4";
45
+ }
46
+ // ─── Implicit socket conversions (Blender-faithful) ──────────────────
47
+ // vec4 appears only on ramp stop-color literals — never linkable, so conversions
48
+ // cover float/color/vector. vector→float is NOT implicit in Blender; rejected.
49
+ export function canConvert(from, to) {
50
+ if (from === to)
51
+ return true;
52
+ if (from === "color" && to === "float")
53
+ return true; // BT.601 via color_to_value
54
+ if (from === "float" && (to === "color" || to === "vector"))
55
+ return true;
56
+ if ((from === "color" && to === "vector") || (from === "vector" && to === "color"))
57
+ return true;
58
+ return false;
59
+ }
60
+ export function convert(from, to, expr) {
61
+ if (from === to)
62
+ return expr;
63
+ if (from === "color" && to === "float")
64
+ return `color_to_value(${expr})`;
65
+ if (from === "float")
66
+ return `vec3f(${expr})`;
67
+ return expr; // color ↔ vector: bit-identical vec3f pass-through
68
+ }
69
+ // ─── Registry ─────────────────────────────────────────────────────────
70
+ const F = (d, requiresLink = false) => ({ type: "float", default: d, requiresLink });
71
+ const C = (d = [1, 1, 1], requiresLink = false) => ({
72
+ type: "color",
73
+ default: d,
74
+ requiresLink,
75
+ });
76
+ const V = (d = [0, 0, 0], requiresLink = false) => ({
77
+ type: "vector",
78
+ default: d,
79
+ requiresLink,
80
+ });
81
+ const V4 = (d) => ({ type: "vec4", default: d });
82
+ const RAMP_INPUTS = {
83
+ fac: F(0.5, true),
84
+ pos0: F(0),
85
+ color0: V4([0, 0, 0, 1]),
86
+ pos1: F(1),
87
+ color1: V4([1, 1, 1, 1]),
88
+ };
89
+ const RAMP_OUTPUTS = { color: "color", alpha: "float", fac_out: "float" };
90
+ // fac_out (.r) matches how a grayscale ramp feeds a scalar consumer in the hand ports —
91
+ // routing through the BT.601 color→float conversion instead would change the value.
92
+ const RAMP_SELECT = { color: ".rgb", alpha: ".a", fac_out: ".r" };
93
+ export const NODE_REGISTRY = {
94
+ // ── Context inputs (template locals; no emission) ──
95
+ texture: {
96
+ inputs: {},
97
+ outputs: { color: "color", alpha: "float" },
98
+ contextOutputs: { color: "tex_color", alpha: "tex_s.a" },
99
+ },
100
+ geometry: {
101
+ inputs: {},
102
+ outputs: {
103
+ normal: "vector",
104
+ view: "vector",
105
+ world_pos: "vector",
106
+ rest_pos: "vector",
107
+ uv: "vector",
108
+ // Blender Texture Coordinate → Reflection (view ray mirrored on the normal);
109
+ // drives env-tracking patterns like metal's voronoi sparkle.
110
+ reflection: "vector",
111
+ },
112
+ contextOutputs: {
113
+ normal: "n",
114
+ view: "v",
115
+ world_pos: "input.worldPos",
116
+ rest_pos: "input.restPos",
117
+ uv: "vec3f(input.uv, 0.0)",
118
+ reflection: "reflect(-v, n)",
119
+ },
120
+ },
121
+ // ── Literals as nodes (for editor ergonomics; inlined literals work too) ──
122
+ value: { inputs: { value: F(0) }, outputs: { value: "float" }, emit: (a) => a.value },
123
+ rgb: { inputs: { color: C() }, outputs: { color: "color" }, emit: (a) => a.color },
124
+ // ── Color ──
125
+ hue_sat: {
126
+ inputs: { hue: F(0.5), saturation: F(1), value: F(1), fac: F(1), color: C([1, 1, 1], true) },
127
+ outputs: { color: "color" },
128
+ emit: (a) => `hue_sat(${a.hue}, ${a.saturation}, ${a.value}, ${a.fac}, ${a.color})`,
129
+ },
130
+ bright_contrast: {
131
+ inputs: { color: C([1, 1, 1], true), bright: F(0), contrast: F(0) },
132
+ outputs: { color: "color" },
133
+ emit: (a) => `bright_contrast(${a.color}, ${a.bright}, ${a.contrast})`,
134
+ },
135
+ invert: {
136
+ inputs: { fac: F(1), color: C([1, 1, 1], true) },
137
+ outputs: { color: "color" },
138
+ emit: (a) => `invert(${a.fac}, ${a.color})`,
139
+ },
140
+ ramp_constant: {
141
+ inputs: RAMP_INPUTS,
142
+ outputs: RAMP_OUTPUTS,
143
+ outputSelect: RAMP_SELECT,
144
+ emit: (a) => `ramp_constant(${a.fac}, ${a.pos0}, ${a.color0}, ${a.pos1}, ${a.color1})`,
145
+ },
146
+ ramp_linear: {
147
+ inputs: RAMP_INPUTS,
148
+ outputs: RAMP_OUTPUTS,
149
+ outputSelect: RAMP_SELECT,
150
+ emit: (a) => `ramp_linear(${a.fac}, ${a.pos0}, ${a.color0}, ${a.pos1}, ${a.color1})`,
151
+ },
152
+ ramp_cardinal: {
153
+ inputs: RAMP_INPUTS,
154
+ outputs: RAMP_OUTPUTS,
155
+ outputSelect: RAMP_SELECT,
156
+ emit: (a) => `ramp_cardinal(${a.fac}, ${a.pos0}, ${a.color0}, ${a.pos1}, ${a.color1})`,
157
+ },
158
+ ramp_constant_aa: {
159
+ inputs: { fac: F(0.5, true), edge: F(0.5), color0: V4([0, 0, 0, 1]), color1: V4([1, 1, 1, 1]) },
160
+ outputs: RAMP_OUTPUTS,
161
+ outputSelect: RAMP_SELECT,
162
+ emit: (a) => `ramp_constant_edge_aa(${a.fac}, ${a.edge}, ${a.color0}, ${a.color1})`,
163
+ },
164
+ // Blender ColorRamp LINEAR with 3 stops black→white→black (triangular peak at 0.5).
165
+ // The only >2-stop ramp the presets use; folded to closed form like the hand port.
166
+ ramp_tri: {
167
+ inputs: { fac: F(0.5, true) },
168
+ outputs: { value: "float" },
169
+ emit: (a) => `1.0 - abs(2.0 * ${a.fac} - 1.0)`,
170
+ },
171
+ // ── Math (enum op in type string) ──
172
+ "math/add": { inputs: { a: F(0), b: F(0) }, outputs: { value: "float" }, emit: (a) => `math_add(${a.a}, ${a.b})` },
173
+ "math/multiply": {
174
+ inputs: { a: F(0), b: F(0) },
175
+ outputs: { value: "float" },
176
+ emit: (a) => `math_multiply(${a.a}, ${a.b})`,
177
+ },
178
+ "math/power": {
179
+ inputs: { a: F(0), b: F(1) },
180
+ outputs: { value: "float" },
181
+ emit: (a) => `math_power(${a.a}, ${a.b})`,
182
+ },
183
+ "math/greater_than": {
184
+ inputs: { a: F(0), b: F(0.5) },
185
+ outputs: { value: "float" },
186
+ emit: (a) => `math_greater_than(${a.a}, ${a.b})`,
187
+ },
188
+ "math/clamp01": { inputs: { a: F(0) }, outputs: { value: "float" }, emit: (a) => `saturate(${a.a})` },
189
+ // ── Mix (blend type in type string) ──
190
+ "mix/blend": {
191
+ inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() },
192
+ outputs: { color: "color" },
193
+ emit: (a) => `mix_blend(${a.fac}, ${a.a}, ${a.b})`,
194
+ },
195
+ "mix/overlay": {
196
+ inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() },
197
+ outputs: { color: "color" },
198
+ emit: (a) => `mix_overlay(${a.fac}, ${a.a}, ${a.b})`,
199
+ },
200
+ "mix/multiply": {
201
+ inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() },
202
+ outputs: { color: "color" },
203
+ emit: (a) => `mix_multiply(${a.fac}, ${a.a}, ${a.b})`,
204
+ },
205
+ "mix/lighten": {
206
+ inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() },
207
+ outputs: { color: "color" },
208
+ emit: (a) => `mix_lighten(${a.fac}, ${a.a}, ${a.b})`,
209
+ },
210
+ "mix/linear_light": {
211
+ inputs: { fac: F(0.5), a: C([1, 1, 1], true), b: C() },
212
+ outputs: { color: "color" },
213
+ emit: (a) => `mix_linear_light(${a.fac}, ${a.a}, ${a.b})`,
214
+ },
215
+ // Emission-add: color + scalar-gated emission. Stand-in for Blender's
216
+ // Emission → Add Shader pair in the ShaderToRGB era (see hair's bright-tex gate).
217
+ "mix/add_emit": {
218
+ inputs: { a: C([0, 0, 0], true), b: F(0) },
219
+ outputs: { color: "color" },
220
+ emit: (a) => `${a.a} + vec3f(${a.b})`,
221
+ },
222
+ // Blender Emission shader: color × strength. In the ShaderToRGB-era ports the
223
+ // emission result feeds a Mix Shader directly as radiance. Color may be a literal
224
+ // (body's rim tints) or linked.
225
+ emission: {
226
+ inputs: { color: C([1, 1, 1]), strength: F(1) },
227
+ outputs: { color: "color" },
228
+ emit: (a) => `${a.color} * ${a.strength}`,
229
+ },
230
+ // Blender Add Shader — radiance sum of two evaluated shading results.
231
+ add_shader: {
232
+ inputs: { a: C([0, 0, 0], true), b: C([0, 0, 0], true) },
233
+ outputs: { color: "color" },
234
+ emit: (a) => `${a.a} + ${a.b}`,
235
+ },
236
+ // Mix Shader — plain lerp between two evaluated shading results.
237
+ mix_shader: {
238
+ inputs: { fac: F(0.5), a: C([0, 0, 0], true), b: C([0, 0, 0]) },
239
+ outputs: { color: "color" },
240
+ emit: (a) => `mix(${a.a}, ${a.b}, ${a.fac})`,
241
+ },
242
+ // ── View-dependent scalars ──
243
+ fresnel: { inputs: { ior: F(1.45) }, outputs: { value: "float" }, emit: (a) => `fresnel(${a.ior}, n, v)` },
244
+ "layer_weight/fresnel": {
245
+ inputs: { blend: F(0.5) },
246
+ outputs: { value: "float" },
247
+ emit: (a) => `layer_weight_fresnel(${a.blend}, n, v)`,
248
+ },
249
+ "layer_weight/facing": {
250
+ inputs: { blend: F(0.5) },
251
+ outputs: { value: "float" },
252
+ emit: (a) => `layer_weight_facing(${a.blend}, n, v)`,
253
+ },
254
+ // ── Lighting capture ──
255
+ shader_to_rgb_diffuse: {
256
+ inputs: {},
257
+ outputs: { value: "float" },
258
+ emit: () => `shader_to_rgb_diffuse(n, l, sun, amb, shadow)`,
259
+ },
260
+ // ── Vector ──
261
+ separate_xyz: {
262
+ inputs: { vector: V([0, 0, 0], true) },
263
+ outputs: { x: "float", y: "float", z: "float" },
264
+ outputSelect: { x: ".x", y: ".y", z: ".z" },
265
+ emit: (a) => a.vector,
266
+ },
267
+ vect_cross: {
268
+ // b may be a literal constant (metal crosses the reflection dir with (0,1,0)).
269
+ inputs: { a: V([0, 0, 0], true), b: V([0, 1, 0]) },
270
+ outputs: { vector: "vector" },
271
+ emit: (a) => `vect_math_cross(${a.a}, ${a.b})`,
272
+ },
273
+ mapping: {
274
+ inputs: { vector: V([0, 0, 0], true), loc: V([0, 0, 0]), rot: V([0, 0, 0]), scl: V([1, 1, 1]) },
275
+ outputs: { vector: "vector" },
276
+ emit: (a) => `mapping_point(${a.vector}, ${a.loc}, ${a.rot}, ${a.scl})`,
277
+ },
278
+ bump: {
279
+ // Screen-space bump; world position comes from context (matches bump_lh's port).
280
+ inputs: { strength: F(0.1), height: F(0, true), normal: V([0, 0, 0], true) },
281
+ outputs: { vector: "vector" },
282
+ emit: (a) => `bump_lh(${a.strength}, ${a.height}, ${a.normal}, input.worldPos)`,
283
+ },
284
+ // ── Procedural textures ──
285
+ tex_noise: {
286
+ inputs: { vector: V([0, 0, 0], true), scale: F(5), detail: F(2), roughness: F(0.5), distortion: F(0) },
287
+ outputs: { value: "float" },
288
+ emit: (a) => `tex_noise(${a.vector}, ${a.scale}, ${a.detail}, ${a.roughness}, ${a.distortion})`,
289
+ },
290
+ tex_gradient: {
291
+ inputs: { vector: V([0, 0, 0], true) },
292
+ outputs: { value: "float" },
293
+ emit: (a) => `tex_gradient_linear(${a.vector})`,
294
+ },
295
+ "tex_voronoi/f1": {
296
+ inputs: { vector: V([0, 0, 0], true), scale: F(5) },
297
+ outputs: { value: "float" },
298
+ emit: (a) => `tex_voronoi_f1(${a.vector}, ${a.scale})`,
299
+ },
300
+ "tex_voronoi/color": {
301
+ inputs: { vector: V([0, 0, 0], true), scale: F(5) },
302
+ outputs: { color: "color" },
303
+ emit: (a) => `tex_voronoi_color(${a.vector}, ${a.scale})`,
304
+ },
305
+ // ── Principled BSDF (frozen 3.6 legacy-EEVEE semantics: eval_principled port) ──
306
+ // normal defaults to the template's shading normal; link a bump/normal_map chain
307
+ // to perturb it (body/cloth_rough noise bump).
308
+ principled: {
309
+ inputs: {
310
+ base: C([0.8, 0.8, 0.8], true),
311
+ metallic: F(0),
312
+ specular: F(0.5),
313
+ roughness: F(0.5),
314
+ spec_clamp: F(1e30),
315
+ sheen: F(0),
316
+ sheen_tint: F(0),
317
+ normal: { type: "vector", contextDefault: "n" },
318
+ },
319
+ outputs: { color: "color" },
320
+ emit: (a) => `eval_principled(PrincipledIn(${a.base}, ${a.metallic}, ${a.specular}, ${a.roughness}, ${a.spec_clamp}, ${a.sheen}, ${a.sheen_tint}), ${a.normal}, l, v, sun, amb, shadow)`,
321
+ },
322
+ };
@@ -0,0 +1,66 @@
1
+ import type { MaterialPreset } from "../engine";
2
+ export type SocketValue = number | [number, number, number] | [number, number, number, number];
3
+ export type GraphNode = {
4
+ /** Unique within the graph, /^[a-z0-9_]+$/ — becomes the WGSL variable suffix. */
5
+ id: string;
6
+ /** Node registry key, e.g. "hue_sat", "math/power", "mix/blend". */
7
+ type: string;
8
+ /** Literal defaults for unlinked input sockets. */
9
+ inputs?: Record<string, SocketValue>;
10
+ /** Editor-only metadata (React Flow position). Ignored by the compiler, round-tripped
11
+ * by serialization so engine-side tooling never strips an editor's layout. */
12
+ ui?: {
13
+ position?: {
14
+ x: number;
15
+ y: number;
16
+ };
17
+ };
18
+ };
19
+ export type GraphLink = {
20
+ from: {
21
+ node: string;
22
+ socket: string;
23
+ };
24
+ to: {
25
+ node: string;
26
+ socket: string;
27
+ };
28
+ };
29
+ /** Adjust-tier slider: overrides one unlinked literal input with a StyleUniforms read. */
30
+ export type ExposedParam = {
31
+ /** Stable key — slot assignment and serialization identity. */
32
+ id: string;
33
+ label: string;
34
+ target: {
35
+ node: string;
36
+ socket: string;
37
+ };
38
+ kind: "float" | "color";
39
+ min?: number;
40
+ max?: number;
41
+ default: SocketValue;
42
+ };
43
+ export type StyleGraph = {
44
+ version: 1;
45
+ name: string;
46
+ /** Which preset slot this style targets. The slot owns pass integration (stencil
47
+ * variants like hair-over-eyes, alpha semantics, draw order) — always on,
48
+ * graph-invisible. The graph only restyles the slot's shading. */
49
+ slot: MaterialPreset;
50
+ nodes: GraphNode[];
51
+ links: GraphLink[];
52
+ /** Must resolve to a color (vec3f) or float (auto-splatted) socket. */
53
+ output: {
54
+ node: string;
55
+ socket: string;
56
+ };
57
+ params?: ExposedParam[];
58
+ };
59
+ export type Diagnostic = {
60
+ severity: "error" | "warning";
61
+ nodeId?: string;
62
+ message: string;
63
+ };
64
+ export declare const MAX_NODES = 64;
65
+ export declare const MAX_PARAMS = 16;
66
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/graph/schema.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAE/C,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AAE9F,MAAM,MAAM,SAAS,GAAG;IACtB,kFAAkF;IAClF,EAAE,EAAE,MAAM,CAAA;IACV,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAA;IACZ,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACpC;mFAC+E;IAC/E,EAAE,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE;YAAE,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAA;CAC7C,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CACrC,CAAA;AAED,0FAA0F;AAC1F,MAAM,MAAM,YAAY,GAAG;IACzB,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAA;IACvB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,WAAW,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,CAAC,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ;;uEAEmE;IACnE,IAAI,EAAE,cAAc,CAAA;IACpB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,KAAK,EAAE,SAAS,EAAE,CAAA;IAClB,uEAAuE;IACvE,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACxC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAA;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,eAAO,MAAM,SAAS,KAAK,CAAA;AAC3B,eAAO,MAAM,UAAU,KAAK,CAAA"}
@@ -0,0 +1,6 @@
1
+ // Material style graph — JSON-serializable node graph compiled to WGSL by compile.ts.
2
+ // Engine-space (LH, Y-up): porting a Blender graph converts coordinates at authoring
3
+ // time (Blender Normal Z → engine Y), the compiler never sees Blender conventions.
4
+ // See docs/graph-compiler-spec.md.
5
+ export const MAX_NODES = 64;
6
+ export const MAX_PARAMS = 16;