@scenography/plugin-wall 0.2.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.
@@ -0,0 +1,84 @@
1
+ let _scenography_scene = require("@scenography/scene");
2
+ //#region src/data/geometry.ts
3
+ const WALL_DEFAULT_HEIGHT = 300;
4
+ const WALL_DEFAULT_THICKNESS = 30;
5
+ /** Number of segments in a wall path (one fewer than vertices unless closed). */
6
+ const segmentCount = (wall) => wall.closed ? wall.path.length : wall.path.length - 1;
7
+ /** Endpoints `[a, b]` of segment `i` (wrapping to the first vertex when closed). */
8
+ const edgeEndpoints = (wall, segment) => {
9
+ const n = wall.path.length;
10
+ return [wall.path[segment % n], wall.path[(segment + 1) % n]];
11
+ };
12
+ /** Every edge name declared across all walls (the anchor namespace for artworks). */
13
+ const allEdgeNames = (walls) => walls.flatMap((w) => (w.edges ?? []).map((e) => e.name));
14
+ //#endregion
15
+ //#region src/data/validate.ts
16
+ const isWallPath = (path) => Array.isArray(path) && path.length >= 2 && path.every((p) => (0, _scenography_scene.isVec2)(p));
17
+ const validateWall = (wall) => {
18
+ const segments = segmentCount(wall);
19
+ const edgeRules = (wall.edges ?? []).flatMap((_, i) => [(0, _scenography_scene.propIs)(`edges[${i}].segment`, (s) => typeof s === "number" && s >= 0 && s < segments, (s) => `segment ${String(s)} is out of range (0..${segments - 1}).`), (0, _scenography_scene.existsProp)(`edges[${i}].name`, "edge is missing a name.")]);
20
+ return (0, _scenography_scene.validate)([
21
+ (0, _scenography_scene.existsProp)("id", "Wall is missing an id."),
22
+ (0, _scenography_scene.nested)("texture", _scenography_scene.validateTexture),
23
+ (0, _scenography_scene.existsProp)("path"),
24
+ (0, _scenography_scene.propIs)("path", isWallPath, "path must have at least two [x, z] vertices."),
25
+ (0, _scenography_scene.when)((w) => isWallPath(w.path), ...edgeRules)
26
+ ])(wall);
27
+ };
28
+ //#endregion
29
+ //#region src/data/plugin.ts
30
+ /**
31
+ * The data half of the wall plugin: owns the `walls` scene key (a collection)
32
+ * and validates each wall. Pure — no three.js. Pass it to `defineScene` so
33
+ * scenes using walls validate without loading the runtime.
34
+ */
35
+ const wallData = {
36
+ kind: "walls",
37
+ cardinality: "many",
38
+ validate: (wall) => validateWall(wall)
39
+ };
40
+ //#endregion
41
+ Object.defineProperty(exports, "WALL_DEFAULT_HEIGHT", {
42
+ enumerable: true,
43
+ get: function() {
44
+ return WALL_DEFAULT_HEIGHT;
45
+ }
46
+ });
47
+ Object.defineProperty(exports, "WALL_DEFAULT_THICKNESS", {
48
+ enumerable: true,
49
+ get: function() {
50
+ return WALL_DEFAULT_THICKNESS;
51
+ }
52
+ });
53
+ Object.defineProperty(exports, "allEdgeNames", {
54
+ enumerable: true,
55
+ get: function() {
56
+ return allEdgeNames;
57
+ }
58
+ });
59
+ Object.defineProperty(exports, "edgeEndpoints", {
60
+ enumerable: true,
61
+ get: function() {
62
+ return edgeEndpoints;
63
+ }
64
+ });
65
+ Object.defineProperty(exports, "segmentCount", {
66
+ enumerable: true,
67
+ get: function() {
68
+ return segmentCount;
69
+ }
70
+ });
71
+ Object.defineProperty(exports, "validateWall", {
72
+ enumerable: true,
73
+ get: function() {
74
+ return validateWall;
75
+ }
76
+ });
77
+ Object.defineProperty(exports, "wallData", {
78
+ enumerable: true,
79
+ get: function() {
80
+ return wallData;
81
+ }
82
+ });
83
+
84
+ //# sourceMappingURL=data-BMWvlhyh.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-BMWvlhyh.cjs","names":["validateTexture"],"sources":["../src/data/geometry.ts","../src/data/validate.ts","../src/data/plugin.ts"],"sourcesContent":["import type { Vec2 } from \"@scenography/scene\";\n\nimport type { Wall } from \"./wall\";\n\nexport const WALL_DEFAULT_HEIGHT = 300;\nexport const WALL_DEFAULT_THICKNESS = 30;\n\n/** Number of segments in a wall path (one fewer than vertices unless closed). */\nexport const segmentCount = (wall: Wall): number =>\n wall.closed ? wall.path.length : wall.path.length - 1;\n\n/** Endpoints `[a, b]` of segment `i` (wrapping to the first vertex when closed). */\nexport const edgeEndpoints = (wall: Wall, segment: number): [Vec2, Vec2] => {\n const n = wall.path.length;\n return [wall.path[segment % n] as Vec2, wall.path[(segment + 1) % n] as Vec2];\n};\n\n/** Every edge name declared across all walls (the anchor namespace for artworks). */\nexport const allEdgeNames = (walls: Wall[]): string[] =>\n walls.flatMap((w) => (w.edges ?? []).map((e) => e.name));\n","import {\n existsProp,\n isVec2,\n nested,\n propIs,\n validate,\n validateTexture,\n when,\n type ValidationResult,\n} from \"@scenography/scene\";\n\nimport { segmentCount } from \"./geometry\";\nimport type { Wall } from \"./wall\";\n\nconst isWallPath = (path: unknown): boolean =>\n Array.isArray(path) && path.length >= 2 && path.every((p) => isVec2(p));\n\nexport const validateWall = (wall: Wall): ValidationResult => {\n const segments = segmentCount(wall);\n // Edge rules are built per declared edge (their paths carry the index); they\n // only run once the path itself is valid, since the range depends on it.\n const edgeRules = (wall.edges ?? []).flatMap((_, i) => [\n propIs(\n `edges[${i}].segment`,\n (s) => typeof s === \"number\" && s >= 0 && s < segments,\n (s) => `segment ${String(s)} is out of range (0..${segments - 1}).`,\n ),\n existsProp(`edges[${i}].name`, \"edge is missing a name.\"),\n ]);\n return validate<Wall>([\n existsProp(\"id\", \"Wall is missing an id.\"),\n nested(\"texture\", validateTexture),\n existsProp(\"path\"),\n propIs(\"path\", isWallPath, \"path must have at least two [x, z] vertices.\"),\n when((w: Wall) => isWallPath(w.path), ...edgeRules),\n ])(wall);\n};\n","import type { DataPlugin } from \"@scenography/scene\";\n\nimport { validateWall } from \"./validate\";\nimport type { Wall } from \"./wall\";\n\n/**\n * The data half of the wall plugin: owns the `walls` scene key (a collection)\n * and validates each wall. Pure — no three.js. Pass it to `defineScene` so\n * scenes using walls validate without loading the runtime.\n */\nexport const wallData: DataPlugin<Wall> = {\n kind: \"walls\",\n cardinality: \"many\",\n validate: (wall) => validateWall(wall),\n};\n"],"mappings":";;AAIA,MAAa,sBAAsB;AACnC,MAAa,yBAAyB;;AAGtC,MAAa,gBAAgB,SAC3B,KAAK,SAAS,KAAK,KAAK,SAAS,KAAK,KAAK,SAAS;;AAGtD,MAAa,iBAAiB,MAAY,YAAkC;CAC1E,MAAM,IAAI,KAAK,KAAK;CACpB,OAAO,CAAC,KAAK,KAAK,UAAU,IAAY,KAAK,MAAM,UAAU,KAAK,EAAU;AAC9E;;AAGA,MAAa,gBAAgB,UAC3B,MAAM,SAAS,OAAO,EAAE,SAAS,CAAC,EAAA,CAAG,KAAK,MAAM,EAAE,IAAI,CAAC;;;ACLzD,MAAM,cAAc,SAClB,MAAM,QAAQ,IAAI,KAAK,KAAK,UAAU,KAAK,KAAK,OAAO,OAAA,GAAA,mBAAA,OAAA,CAAa,CAAC,CAAC;AAExE,MAAa,gBAAgB,SAAiC;CAC5D,MAAM,WAAW,aAAa,IAAI;CAGlC,MAAM,aAAa,KAAK,SAAS,CAAC,EAAA,CAAG,SAAS,GAAG,MAAM,EAAA,GAAA,mBAAA,OAAA,CAEnD,SAAS,EAAE,aACV,MAAM,OAAO,MAAM,YAAY,KAAK,KAAK,IAAI,WAC7C,MAAM,WAAW,OAAO,CAAC,EAAE,uBAAuB,WAAW,EAAE,GAClE,IAAA,GAAA,mBAAA,WAAA,CACW,SAAS,EAAE,SAAS,yBAAyB,CAC1D,CAAC;CACD,QAAA,GAAA,mBAAA,SAAA,CAAsB;qCACT,MAAM,wBAAwB;iCAClC,WAAWA,mBAAAA,eAAe;qCACtB,MAAM;iCACV,QAAQ,YAAY,8CAA8C;gCACnE,MAAY,WAAW,EAAE,IAAI,GAAG,GAAG,SAAS;CACpD,CAAC,CAAC,CAAC,IAAI;AACT;;;;;;;;AC1BA,MAAa,WAA6B;CACxC,MAAM;CACN,aAAa;CACb,WAAW,SAAS,aAAa,IAAI;AACvC"}
@@ -0,0 +1,43 @@
1
+ import { existsProp, isVec2, nested, propIs, validate, validateTexture, when } from "@scenography/scene";
2
+ //#region src/data/geometry.ts
3
+ const WALL_DEFAULT_HEIGHT = 300;
4
+ const WALL_DEFAULT_THICKNESS = 30;
5
+ /** Number of segments in a wall path (one fewer than vertices unless closed). */
6
+ const segmentCount = (wall) => wall.closed ? wall.path.length : wall.path.length - 1;
7
+ /** Endpoints `[a, b]` of segment `i` (wrapping to the first vertex when closed). */
8
+ const edgeEndpoints = (wall, segment) => {
9
+ const n = wall.path.length;
10
+ return [wall.path[segment % n], wall.path[(segment + 1) % n]];
11
+ };
12
+ /** Every edge name declared across all walls (the anchor namespace for artworks). */
13
+ const allEdgeNames = (walls) => walls.flatMap((w) => (w.edges ?? []).map((e) => e.name));
14
+ //#endregion
15
+ //#region src/data/validate.ts
16
+ const isWallPath = (path) => Array.isArray(path) && path.length >= 2 && path.every((p) => isVec2(p));
17
+ const validateWall = (wall) => {
18
+ const segments = segmentCount(wall);
19
+ const edgeRules = (wall.edges ?? []).flatMap((_, i) => [propIs(`edges[${i}].segment`, (s) => typeof s === "number" && s >= 0 && s < segments, (s) => `segment ${String(s)} is out of range (0..${segments - 1}).`), existsProp(`edges[${i}].name`, "edge is missing a name.")]);
20
+ return validate([
21
+ existsProp("id", "Wall is missing an id."),
22
+ nested("texture", validateTexture),
23
+ existsProp("path"),
24
+ propIs("path", isWallPath, "path must have at least two [x, z] vertices."),
25
+ when((w) => isWallPath(w.path), ...edgeRules)
26
+ ])(wall);
27
+ };
28
+ //#endregion
29
+ //#region src/data/plugin.ts
30
+ /**
31
+ * The data half of the wall plugin: owns the `walls` scene key (a collection)
32
+ * and validates each wall. Pure — no three.js. Pass it to `defineScene` so
33
+ * scenes using walls validate without loading the runtime.
34
+ */
35
+ const wallData = {
36
+ kind: "walls",
37
+ cardinality: "many",
38
+ validate: (wall) => validateWall(wall)
39
+ };
40
+ //#endregion
41
+ export { allEdgeNames as a, WALL_DEFAULT_THICKNESS as i, validateWall as n, edgeEndpoints as o, WALL_DEFAULT_HEIGHT as r, segmentCount as s, wallData as t };
42
+
43
+ //# sourceMappingURL=data-PI34E7UF.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-PI34E7UF.mjs","names":[],"sources":["../src/data/geometry.ts","../src/data/validate.ts","../src/data/plugin.ts"],"sourcesContent":["import type { Vec2 } from \"@scenography/scene\";\n\nimport type { Wall } from \"./wall\";\n\nexport const WALL_DEFAULT_HEIGHT = 300;\nexport const WALL_DEFAULT_THICKNESS = 30;\n\n/** Number of segments in a wall path (one fewer than vertices unless closed). */\nexport const segmentCount = (wall: Wall): number =>\n wall.closed ? wall.path.length : wall.path.length - 1;\n\n/** Endpoints `[a, b]` of segment `i` (wrapping to the first vertex when closed). */\nexport const edgeEndpoints = (wall: Wall, segment: number): [Vec2, Vec2] => {\n const n = wall.path.length;\n return [wall.path[segment % n] as Vec2, wall.path[(segment + 1) % n] as Vec2];\n};\n\n/** Every edge name declared across all walls (the anchor namespace for artworks). */\nexport const allEdgeNames = (walls: Wall[]): string[] =>\n walls.flatMap((w) => (w.edges ?? []).map((e) => e.name));\n","import {\n existsProp,\n isVec2,\n nested,\n propIs,\n validate,\n validateTexture,\n when,\n type ValidationResult,\n} from \"@scenography/scene\";\n\nimport { segmentCount } from \"./geometry\";\nimport type { Wall } from \"./wall\";\n\nconst isWallPath = (path: unknown): boolean =>\n Array.isArray(path) && path.length >= 2 && path.every((p) => isVec2(p));\n\nexport const validateWall = (wall: Wall): ValidationResult => {\n const segments = segmentCount(wall);\n // Edge rules are built per declared edge (their paths carry the index); they\n // only run once the path itself is valid, since the range depends on it.\n const edgeRules = (wall.edges ?? []).flatMap((_, i) => [\n propIs(\n `edges[${i}].segment`,\n (s) => typeof s === \"number\" && s >= 0 && s < segments,\n (s) => `segment ${String(s)} is out of range (0..${segments - 1}).`,\n ),\n existsProp(`edges[${i}].name`, \"edge is missing a name.\"),\n ]);\n return validate<Wall>([\n existsProp(\"id\", \"Wall is missing an id.\"),\n nested(\"texture\", validateTexture),\n existsProp(\"path\"),\n propIs(\"path\", isWallPath, \"path must have at least two [x, z] vertices.\"),\n when((w: Wall) => isWallPath(w.path), ...edgeRules),\n ])(wall);\n};\n","import type { DataPlugin } from \"@scenography/scene\";\n\nimport { validateWall } from \"./validate\";\nimport type { Wall } from \"./wall\";\n\n/**\n * The data half of the wall plugin: owns the `walls` scene key (a collection)\n * and validates each wall. Pure — no three.js. Pass it to `defineScene` so\n * scenes using walls validate without loading the runtime.\n */\nexport const wallData: DataPlugin<Wall> = {\n kind: \"walls\",\n cardinality: \"many\",\n validate: (wall) => validateWall(wall),\n};\n"],"mappings":";;AAIA,MAAa,sBAAsB;AACnC,MAAa,yBAAyB;;AAGtC,MAAa,gBAAgB,SAC3B,KAAK,SAAS,KAAK,KAAK,SAAS,KAAK,KAAK,SAAS;;AAGtD,MAAa,iBAAiB,MAAY,YAAkC;CAC1E,MAAM,IAAI,KAAK,KAAK;CACpB,OAAO,CAAC,KAAK,KAAK,UAAU,IAAY,KAAK,MAAM,UAAU,KAAK,EAAU;AAC9E;;AAGA,MAAa,gBAAgB,UAC3B,MAAM,SAAS,OAAO,EAAE,SAAS,CAAC,EAAA,CAAG,KAAK,MAAM,EAAE,IAAI,CAAC;;;ACLzD,MAAM,cAAc,SAClB,MAAM,QAAQ,IAAI,KAAK,KAAK,UAAU,KAAK,KAAK,OAAO,MAAM,OAAO,CAAC,CAAC;AAExE,MAAa,gBAAgB,SAAiC;CAC5D,MAAM,WAAW,aAAa,IAAI;CAGlC,MAAM,aAAa,KAAK,SAAS,CAAC,EAAA,CAAG,SAAS,GAAG,MAAM,CACrD,OACE,SAAS,EAAE,aACV,MAAM,OAAO,MAAM,YAAY,KAAK,KAAK,IAAI,WAC7C,MAAM,WAAW,OAAO,CAAC,EAAE,uBAAuB,WAAW,EAAE,GAClE,GACA,WAAW,SAAS,EAAE,SAAS,yBAAyB,CAC1D,CAAC;CACD,OAAO,SAAe;EACpB,WAAW,MAAM,wBAAwB;EACzC,OAAO,WAAW,eAAe;EACjC,WAAW,MAAM;EACjB,OAAO,QAAQ,YAAY,8CAA8C;EACzE,MAAM,MAAY,WAAW,EAAE,IAAI,GAAG,GAAG,SAAS;CACpD,CAAC,CAAC,CAAC,IAAI;AACT;;;;;;;;AC1BA,MAAa,WAA6B;CACxC,MAAM;CACN,aAAa;CACb,WAAW,SAAS,aAAa,IAAI;AACvC"}
package/dist/data.cjs ADDED
@@ -0,0 +1,9 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_data = require("./data-BMWvlhyh.cjs");
3
+ exports.WALL_DEFAULT_HEIGHT = require_data.WALL_DEFAULT_HEIGHT;
4
+ exports.WALL_DEFAULT_THICKNESS = require_data.WALL_DEFAULT_THICKNESS;
5
+ exports.allEdgeNames = require_data.allEdgeNames;
6
+ exports.edgeEndpoints = require_data.edgeEndpoints;
7
+ exports.segmentCount = require_data.segmentCount;
8
+ exports.validateWall = require_data.validateWall;
9
+ exports.wallData = require_data.wallData;
@@ -0,0 +1,2 @@
1
+ import { a as allEdgeNames, c as NamedEdge, i as WALL_DEFAULT_THICKNESS, l as Wall, n as validateWall, o as edgeEndpoints, r as WALL_DEFAULT_HEIGHT, s as segmentCount, t as wallData } from "./index-tssS_Ed-.cjs";
2
+ export { type NamedEdge, WALL_DEFAULT_HEIGHT, WALL_DEFAULT_THICKNESS, type Wall, allEdgeNames, edgeEndpoints, segmentCount, validateWall, wallData };
@@ -0,0 +1,2 @@
1
+ import { a as allEdgeNames, c as NamedEdge, i as WALL_DEFAULT_THICKNESS, l as Wall, n as validateWall, o as edgeEndpoints, r as WALL_DEFAULT_HEIGHT, s as segmentCount, t as wallData } from "./index-tssS_Ed-.mjs";
2
+ export { type NamedEdge, WALL_DEFAULT_HEIGHT, WALL_DEFAULT_THICKNESS, type Wall, allEdgeNames, edgeEndpoints, segmentCount, validateWall, wallData };
package/dist/data.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { a as allEdgeNames, i as WALL_DEFAULT_THICKNESS, n as validateWall, o as edgeEndpoints, r as WALL_DEFAULT_HEIGHT, s as segmentCount, t as wallData } from "./data-PI34E7UF.mjs";
2
+ export { WALL_DEFAULT_HEIGHT, WALL_DEFAULT_THICKNESS, allEdgeNames, edgeEndpoints, segmentCount, validateWall, wallData };
@@ -0,0 +1,66 @@
1
+ import { DataPlugin, SurfaceTexture, ValidationResult, Vec2 } from "@scenography/scene";
2
+
3
+ //#region src/data/wall.d.ts
4
+ /**
5
+ * Names a segment of a wall's path so artworks can hang on it. The segment runs
6
+ * from `path[segment]` to `path[segment + 1]` (wrapping to the first vertex when
7
+ * the wall is `closed`).
8
+ */
9
+ interface NamedEdge {
10
+ segment: number;
11
+ /** Unique anchor name, referenced by an artwork's `location.edge`. */
12
+ name: string;
13
+ }
14
+ /**
15
+ * A wall as a top-down path with thickness and height. The engine extrudes the
16
+ * polyline into a continuous run of walls — no need to declare each side
17
+ * separately. Corners are simple butt joints for now (the path is followed at
18
+ * its center); chamfers/rounding and inside/outside offset are future work.
19
+ */
20
+ interface Wall {
21
+ id: string;
22
+ /** Top-down footprint as `[x, z]` vertices; the wall follows this polyline. */
23
+ path: Vec2[];
24
+ /** Wall height in world units. Defaults to `300` (≈3 m). */
25
+ height?: number;
26
+ /** Wall thickness in world units. Defaults to `30`. */
27
+ thickness?: number;
28
+ /** Connect the last vertex back to the first. Defaults to `false`. */
29
+ closed?: boolean;
30
+ /** Named segments that artworks can hang on. */
31
+ edges?: NamedEdge[];
32
+ /** CSS-style color of the wall surface. Defaults to a neutral white. */
33
+ color?: string;
34
+ /** Image mapped onto the surface. Tints with `color` when both are set. */
35
+ texture?: SurfaceTexture;
36
+ }
37
+ declare module "@scenography/scene" {
38
+ interface Scene {
39
+ /** Walls in the space — provided by `@scenography/plugin-wall`. */
40
+ walls?: Wall[];
41
+ }
42
+ }
43
+ //#endregion
44
+ //#region src/data/geometry.d.ts
45
+ declare const WALL_DEFAULT_HEIGHT = 300;
46
+ declare const WALL_DEFAULT_THICKNESS = 30;
47
+ /** Number of segments in a wall path (one fewer than vertices unless closed). */
48
+ declare const segmentCount: (wall: Wall) => number;
49
+ /** Endpoints `[a, b]` of segment `i` (wrapping to the first vertex when closed). */
50
+ declare const edgeEndpoints: (wall: Wall, segment: number) => [Vec2, Vec2];
51
+ /** Every edge name declared across all walls (the anchor namespace for artworks). */
52
+ declare const allEdgeNames: (walls: Wall[]) => string[];
53
+ //#endregion
54
+ //#region src/data/validate.d.ts
55
+ declare const validateWall: (wall: Wall) => ValidationResult;
56
+ //#endregion
57
+ //#region src/data/plugin.d.ts
58
+ /**
59
+ * The data half of the wall plugin: owns the `walls` scene key (a collection)
60
+ * and validates each wall. Pure — no three.js. Pass it to `defineScene` so
61
+ * scenes using walls validate without loading the runtime.
62
+ */
63
+ declare const wallData: DataPlugin<Wall>;
64
+ //#endregion
65
+ export { allEdgeNames as a, NamedEdge as c, WALL_DEFAULT_THICKNESS as i, Wall as l, validateWall as n, edgeEndpoints as o, WALL_DEFAULT_HEIGHT as r, segmentCount as s, wallData as t };
66
+ //# sourceMappingURL=index-tssS_Ed-.d.cts.map
@@ -0,0 +1,66 @@
1
+ import { DataPlugin, SurfaceTexture, ValidationResult, Vec2 } from "@scenography/scene";
2
+
3
+ //#region src/data/wall.d.ts
4
+ /**
5
+ * Names a segment of a wall's path so artworks can hang on it. The segment runs
6
+ * from `path[segment]` to `path[segment + 1]` (wrapping to the first vertex when
7
+ * the wall is `closed`).
8
+ */
9
+ interface NamedEdge {
10
+ segment: number;
11
+ /** Unique anchor name, referenced by an artwork's `location.edge`. */
12
+ name: string;
13
+ }
14
+ /**
15
+ * A wall as a top-down path with thickness and height. The engine extrudes the
16
+ * polyline into a continuous run of walls — no need to declare each side
17
+ * separately. Corners are simple butt joints for now (the path is followed at
18
+ * its center); chamfers/rounding and inside/outside offset are future work.
19
+ */
20
+ interface Wall {
21
+ id: string;
22
+ /** Top-down footprint as `[x, z]` vertices; the wall follows this polyline. */
23
+ path: Vec2[];
24
+ /** Wall height in world units. Defaults to `300` (≈3 m). */
25
+ height?: number;
26
+ /** Wall thickness in world units. Defaults to `30`. */
27
+ thickness?: number;
28
+ /** Connect the last vertex back to the first. Defaults to `false`. */
29
+ closed?: boolean;
30
+ /** Named segments that artworks can hang on. */
31
+ edges?: NamedEdge[];
32
+ /** CSS-style color of the wall surface. Defaults to a neutral white. */
33
+ color?: string;
34
+ /** Image mapped onto the surface. Tints with `color` when both are set. */
35
+ texture?: SurfaceTexture;
36
+ }
37
+ declare module "@scenography/scene" {
38
+ interface Scene {
39
+ /** Walls in the space — provided by `@scenography/plugin-wall`. */
40
+ walls?: Wall[];
41
+ }
42
+ }
43
+ //#endregion
44
+ //#region src/data/geometry.d.ts
45
+ declare const WALL_DEFAULT_HEIGHT = 300;
46
+ declare const WALL_DEFAULT_THICKNESS = 30;
47
+ /** Number of segments in a wall path (one fewer than vertices unless closed). */
48
+ declare const segmentCount: (wall: Wall) => number;
49
+ /** Endpoints `[a, b]` of segment `i` (wrapping to the first vertex when closed). */
50
+ declare const edgeEndpoints: (wall: Wall, segment: number) => [Vec2, Vec2];
51
+ /** Every edge name declared across all walls (the anchor namespace for artworks). */
52
+ declare const allEdgeNames: (walls: Wall[]) => string[];
53
+ //#endregion
54
+ //#region src/data/validate.d.ts
55
+ declare const validateWall: (wall: Wall) => ValidationResult;
56
+ //#endregion
57
+ //#region src/data/plugin.d.ts
58
+ /**
59
+ * The data half of the wall plugin: owns the `walls` scene key (a collection)
60
+ * and validates each wall. Pure — no three.js. Pass it to `defineScene` so
61
+ * scenes using walls validate without loading the runtime.
62
+ */
63
+ declare const wallData: DataPlugin<Wall>;
64
+ //#endregion
65
+ export { allEdgeNames as a, NamedEdge as c, WALL_DEFAULT_THICKNESS as i, Wall as l, validateWall as n, edgeEndpoints as o, WALL_DEFAULT_HEIGHT as r, segmentCount as s, wallData as t };
66
+ //# sourceMappingURL=index-tssS_Ed-.d.mts.map
package/dist/index.cjs ADDED
@@ -0,0 +1,121 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_data = require("./data-BMWvlhyh.cjs");
3
+ let _scenography_engine = require("@scenography/engine");
4
+ let three = require("three");
5
+ //#region src/buildWall.ts
6
+ /**
7
+ * Build the three.js object for a `Wall`: one thickened box per path segment,
8
+ * following the polyline at its center. Corners are simple butt joints (boxes
9
+ * overlap slightly), which reads cleanly at a uniform thickness.
10
+ */
11
+ function buildWall(wall) {
12
+ const height = wall.height ?? 300;
13
+ const thickness = wall.thickness ?? 30;
14
+ const material = new three.MeshStandardMaterial({ color: wall.color ?? "#ffffff" });
15
+ const needsUv1 = wall.texture ? (0, _scenography_engine.applyTexture)(material, wall.texture) : false;
16
+ const group = new three.Group();
17
+ const segments = require_data.segmentCount(wall);
18
+ for (let i = 0; i < segments; i++) {
19
+ const [a, b] = require_data.edgeEndpoints(wall, i);
20
+ const dx = b[0] - a[0];
21
+ const dz = b[1] - a[1];
22
+ const length = Math.hypot(dx, dz);
23
+ if (length === 0) continue;
24
+ const geometry = new three.BoxGeometry(length, height, thickness);
25
+ if (needsUv1) {
26
+ const uv = geometry.getAttribute("uv");
27
+ if (uv) geometry.setAttribute("uv1", uv);
28
+ }
29
+ const box = new three.Mesh(geometry, material);
30
+ box.position.set((a[0] + b[0]) / 2, height / 2, (a[1] + b[1]) / 2);
31
+ box.rotation.y = Math.atan2(-dz, dx);
32
+ box.castShadow = true;
33
+ box.receiveShadow = true;
34
+ group.add(box);
35
+ }
36
+ group.name = `wall:${wall.id}`;
37
+ group.userData.scenographyId = wall.id;
38
+ return group;
39
+ }
40
+ //#endregion
41
+ //#region src/colliders.ts
42
+ /**
43
+ * Build a collision box for every wall segment (matching the built geometry) —
44
+ * the `colliders` capability the camera controls consume to keep the visitor
45
+ * out of walls.
46
+ */
47
+ function buildColliders(walls) {
48
+ const colliders = [];
49
+ for (const wall of walls) {
50
+ const halfThickness = (wall.thickness ?? 30) / 2;
51
+ const segments = require_data.segmentCount(wall);
52
+ for (let i = 0; i < segments; i++) {
53
+ const [a, b] = require_data.edgeEndpoints(wall, i);
54
+ const ex = b[0] - a[0];
55
+ const ez = b[1] - a[1];
56
+ const length = Math.hypot(ex, ez);
57
+ if (length === 0) continue;
58
+ colliders.push({
59
+ cx: (a[0] + b[0]) / 2,
60
+ cz: (a[1] + b[1]) / 2,
61
+ dx: ex / length,
62
+ dz: ez / length,
63
+ halfLength: length / 2,
64
+ halfThickness
65
+ });
66
+ }
67
+ }
68
+ return colliders;
69
+ }
70
+ //#endregion
71
+ //#region src/edgeIndex.ts
72
+ /**
73
+ * Build the `name -> EdgeAnchor` index across every wall — the `edges`
74
+ * capability the artwork plugin consumes to hang pieces on a named segment.
75
+ */
76
+ function indexEdges(walls) {
77
+ const index = /* @__PURE__ */ new Map();
78
+ for (const wall of walls) {
79
+ const height = wall.height ?? 300;
80
+ const thickness = wall.thickness ?? 30;
81
+ for (const edge of wall.edges ?? []) {
82
+ const [a, b] = require_data.edgeEndpoints(wall, edge.segment);
83
+ index.set(edge.name, {
84
+ a,
85
+ b,
86
+ height,
87
+ thickness
88
+ });
89
+ }
90
+ }
91
+ return index;
92
+ }
93
+ //#endregion
94
+ //#region src/plugin.ts
95
+ /**
96
+ * The wall plugin: extrudes each `Wall` into geometry, and — across all walls —
97
+ * publishes the `edges` capability (named anchors artworks hang on) and the
98
+ * `colliders` capability (obstacles the camera can't pass). A single change
99
+ * token covers both, so consumers only rebuild when the walls actually change.
100
+ */
101
+ const wall = () => ({
102
+ data: require_data.wallData,
103
+ build: (definition) => ({ object: buildWall(definition) }),
104
+ provide: (walls, ctx) => {
105
+ const token = JSON.stringify(walls);
106
+ ctx.capabilities.provide(_scenography_engine.EDGES, indexEdges(walls), token);
107
+ ctx.capabilities.provide(_scenography_engine.COLLIDERS, buildColliders(walls), token);
108
+ }
109
+ });
110
+ //#endregion
111
+ exports.WALL_DEFAULT_HEIGHT = require_data.WALL_DEFAULT_HEIGHT;
112
+ exports.WALL_DEFAULT_THICKNESS = require_data.WALL_DEFAULT_THICKNESS;
113
+ exports.allEdgeNames = require_data.allEdgeNames;
114
+ exports.buildColliders = buildColliders;
115
+ exports.buildWall = buildWall;
116
+ exports.indexEdges = indexEdges;
117
+ exports.validateWall = require_data.validateWall;
118
+ exports.wall = wall;
119
+ exports.wallData = require_data.wallData;
120
+
121
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["MeshStandardMaterial","Group","segmentCount","edgeEndpoints","BoxGeometry","Mesh","segmentCount","edgeEndpoints","edgeEndpoints","wallData","EDGES","COLLIDERS"],"sources":["../src/buildWall.ts","../src/colliders.ts","../src/edgeIndex.ts","../src/plugin.ts"],"sourcesContent":["import { applyTexture } from \"@scenography/engine\";\nimport { BoxGeometry, Group, Mesh, MeshStandardMaterial, type Object3D } from \"three\";\n\nimport {\n WALL_DEFAULT_HEIGHT,\n WALL_DEFAULT_THICKNESS,\n edgeEndpoints,\n segmentCount,\n type Wall,\n} from \"./data\";\n\n/**\n * Build the three.js object for a `Wall`: one thickened box per path segment,\n * following the polyline at its center. Corners are simple butt joints (boxes\n * overlap slightly), which reads cleanly at a uniform thickness.\n */\nexport function buildWall(wall: Wall): Object3D {\n const height = wall.height ?? WALL_DEFAULT_HEIGHT;\n const thickness = wall.thickness ?? WALL_DEFAULT_THICKNESS;\n const material = new MeshStandardMaterial({ color: wall.color ?? \"#ffffff\" });\n const needsUv1 = wall.texture ? applyTexture(material, wall.texture) : false;\n\n const group = new Group();\n const segments = segmentCount(wall);\n for (let i = 0; i < segments; i++) {\n const [a, b] = edgeEndpoints(wall, i);\n const dx = b[0] - a[0];\n const dz = b[1] - a[1];\n const length = Math.hypot(dx, dz);\n if (length === 0) continue;\n\n const geometry = new BoxGeometry(length, height, thickness);\n // aoMap reads the second UV set; box geometry only ships `uv`, so mirror it.\n if (needsUv1) {\n const uv = geometry.getAttribute(\"uv\");\n if (uv) geometry.setAttribute(\"uv1\", uv);\n }\n\n const box = new Mesh(geometry, material);\n box.position.set((a[0] + b[0]) / 2, height / 2, (a[1] + b[1]) / 2);\n // Align the box's local X (its length) with the segment direction.\n box.rotation.y = Math.atan2(-dz, dx);\n box.castShadow = true;\n box.receiveShadow = true;\n group.add(box);\n }\n\n group.name = `wall:${wall.id}`;\n group.userData.scenographyId = wall.id;\n return group;\n}\n","import type { Collider } from \"@scenography/engine\";\n\nimport { WALL_DEFAULT_THICKNESS, edgeEndpoints, segmentCount, type Wall } from \"./data\";\n\n/**\n * Build a collision box for every wall segment (matching the built geometry) —\n * the `colliders` capability the camera controls consume to keep the visitor\n * out of walls.\n */\nexport function buildColliders(walls: Wall[]): Collider[] {\n const colliders: Collider[] = [];\n for (const wall of walls) {\n const halfThickness = (wall.thickness ?? WALL_DEFAULT_THICKNESS) / 2;\n const segments = segmentCount(wall);\n for (let i = 0; i < segments; i++) {\n const [a, b] = edgeEndpoints(wall, i);\n const ex = b[0] - a[0];\n const ez = b[1] - a[1];\n const length = Math.hypot(ex, ez);\n if (length === 0) continue;\n colliders.push({\n cx: (a[0] + b[0]) / 2,\n cz: (a[1] + b[1]) / 2,\n dx: ex / length,\n dz: ez / length,\n halfLength: length / 2,\n halfThickness,\n });\n }\n }\n return colliders;\n}\n","import type { EdgeAnchor, EdgeIndex } from \"@scenography/engine\";\n\nimport {\n WALL_DEFAULT_HEIGHT,\n WALL_DEFAULT_THICKNESS,\n edgeEndpoints,\n type Wall,\n} from \"./data\";\n\n/**\n * Build the `name -> EdgeAnchor` index across every wall — the `edges`\n * capability the artwork plugin consumes to hang pieces on a named segment.\n */\nexport function indexEdges(walls: Wall[]): EdgeIndex {\n const index = new Map<string, EdgeAnchor>();\n for (const wall of walls) {\n const height = wall.height ?? WALL_DEFAULT_HEIGHT;\n const thickness = wall.thickness ?? WALL_DEFAULT_THICKNESS;\n for (const edge of wall.edges ?? []) {\n const [a, b] = edgeEndpoints(wall, edge.segment);\n index.set(edge.name, { a, b, height, thickness });\n }\n }\n return index;\n}\n","import { COLLIDERS, EDGES, type EnginePlugin } from \"@scenography/engine\";\n\nimport { buildWall } from \"./buildWall\";\nimport { buildColliders } from \"./colliders\";\nimport { wallData, type Wall } from \"./data\";\nimport { indexEdges } from \"./edgeIndex\";\n\n/**\n * The wall plugin: extrudes each `Wall` into geometry, and — across all walls —\n * publishes the `edges` capability (named anchors artworks hang on) and the\n * `colliders` capability (obstacles the camera can't pass). A single change\n * token covers both, so consumers only rebuild when the walls actually change.\n */\nexport const wall = (): EnginePlugin<Wall> => ({\n data: wallData,\n build: (definition) => ({ object: buildWall(definition) }),\n provide: (walls, ctx) => {\n const token = JSON.stringify(walls);\n ctx.capabilities.provide(EDGES, indexEdges(walls), token);\n ctx.capabilities.provide(COLLIDERS, buildColliders(walls), token);\n },\n});\n"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { a as allEdgeNames, c as NamedEdge, i as WALL_DEFAULT_THICKNESS, l as Wall, n as validateWall, r as WALL_DEFAULT_HEIGHT, t as wallData } from "./index-tssS_Ed-.cjs";
2
+ import { Collider, EdgeIndex, EnginePlugin } from "@scenography/engine";
3
+ import { Object3D } from "three";
4
+
5
+ //#region src/plugin.d.ts
6
+ /**
7
+ * The wall plugin: extrudes each `Wall` into geometry, and — across all walls —
8
+ * publishes the `edges` capability (named anchors artworks hang on) and the
9
+ * `colliders` capability (obstacles the camera can't pass). A single change
10
+ * token covers both, so consumers only rebuild when the walls actually change.
11
+ */
12
+ declare const wall: () => EnginePlugin<Wall>;
13
+ //#endregion
14
+ //#region src/buildWall.d.ts
15
+ /**
16
+ * Build the three.js object for a `Wall`: one thickened box per path segment,
17
+ * following the polyline at its center. Corners are simple butt joints (boxes
18
+ * overlap slightly), which reads cleanly at a uniform thickness.
19
+ */
20
+ declare function buildWall(wall: Wall): Object3D;
21
+ //#endregion
22
+ //#region src/colliders.d.ts
23
+ /**
24
+ * Build a collision box for every wall segment (matching the built geometry) —
25
+ * the `colliders` capability the camera controls consume to keep the visitor
26
+ * out of walls.
27
+ */
28
+ declare function buildColliders(walls: Wall[]): Collider[];
29
+ //#endregion
30
+ //#region src/edgeIndex.d.ts
31
+ /**
32
+ * Build the `name -> EdgeAnchor` index across every wall — the `edges`
33
+ * capability the artwork plugin consumes to hang pieces on a named segment.
34
+ */
35
+ declare function indexEdges(walls: Wall[]): EdgeIndex;
36
+ //#endregion
37
+ export { type NamedEdge, WALL_DEFAULT_HEIGHT, WALL_DEFAULT_THICKNESS, type Wall, allEdgeNames, buildColliders, buildWall, indexEdges, validateWall, wall, wallData };
38
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,38 @@
1
+ import { a as allEdgeNames, c as NamedEdge, i as WALL_DEFAULT_THICKNESS, l as Wall, n as validateWall, r as WALL_DEFAULT_HEIGHT, t as wallData } from "./index-tssS_Ed-.mjs";
2
+ import { Collider, EdgeIndex, EnginePlugin } from "@scenography/engine";
3
+ import { Object3D } from "three";
4
+
5
+ //#region src/plugin.d.ts
6
+ /**
7
+ * The wall plugin: extrudes each `Wall` into geometry, and — across all walls —
8
+ * publishes the `edges` capability (named anchors artworks hang on) and the
9
+ * `colliders` capability (obstacles the camera can't pass). A single change
10
+ * token covers both, so consumers only rebuild when the walls actually change.
11
+ */
12
+ declare const wall: () => EnginePlugin<Wall>;
13
+ //#endregion
14
+ //#region src/buildWall.d.ts
15
+ /**
16
+ * Build the three.js object for a `Wall`: one thickened box per path segment,
17
+ * following the polyline at its center. Corners are simple butt joints (boxes
18
+ * overlap slightly), which reads cleanly at a uniform thickness.
19
+ */
20
+ declare function buildWall(wall: Wall): Object3D;
21
+ //#endregion
22
+ //#region src/colliders.d.ts
23
+ /**
24
+ * Build a collision box for every wall segment (matching the built geometry) —
25
+ * the `colliders` capability the camera controls consume to keep the visitor
26
+ * out of walls.
27
+ */
28
+ declare function buildColliders(walls: Wall[]): Collider[];
29
+ //#endregion
30
+ //#region src/edgeIndex.d.ts
31
+ /**
32
+ * Build the `name -> EdgeAnchor` index across every wall — the `edges`
33
+ * capability the artwork plugin consumes to hang pieces on a named segment.
34
+ */
35
+ declare function indexEdges(walls: Wall[]): EdgeIndex;
36
+ //#endregion
37
+ export { type NamedEdge, WALL_DEFAULT_HEIGHT, WALL_DEFAULT_THICKNESS, type Wall, allEdgeNames, buildColliders, buildWall, indexEdges, validateWall, wall, wallData };
38
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,112 @@
1
+ import { a as allEdgeNames, i as WALL_DEFAULT_THICKNESS, n as validateWall, o as edgeEndpoints, r as WALL_DEFAULT_HEIGHT, s as segmentCount, t as wallData } from "./data-PI34E7UF.mjs";
2
+ import { COLLIDERS, EDGES, applyTexture } from "@scenography/engine";
3
+ import { BoxGeometry, Group, Mesh, MeshStandardMaterial } from "three";
4
+ //#region src/buildWall.ts
5
+ /**
6
+ * Build the three.js object for a `Wall`: one thickened box per path segment,
7
+ * following the polyline at its center. Corners are simple butt joints (boxes
8
+ * overlap slightly), which reads cleanly at a uniform thickness.
9
+ */
10
+ function buildWall(wall) {
11
+ const height = wall.height ?? 300;
12
+ const thickness = wall.thickness ?? 30;
13
+ const material = new MeshStandardMaterial({ color: wall.color ?? "#ffffff" });
14
+ const needsUv1 = wall.texture ? applyTexture(material, wall.texture) : false;
15
+ const group = new Group();
16
+ const segments = segmentCount(wall);
17
+ for (let i = 0; i < segments; i++) {
18
+ const [a, b] = edgeEndpoints(wall, i);
19
+ const dx = b[0] - a[0];
20
+ const dz = b[1] - a[1];
21
+ const length = Math.hypot(dx, dz);
22
+ if (length === 0) continue;
23
+ const geometry = new BoxGeometry(length, height, thickness);
24
+ if (needsUv1) {
25
+ const uv = geometry.getAttribute("uv");
26
+ if (uv) geometry.setAttribute("uv1", uv);
27
+ }
28
+ const box = new Mesh(geometry, material);
29
+ box.position.set((a[0] + b[0]) / 2, height / 2, (a[1] + b[1]) / 2);
30
+ box.rotation.y = Math.atan2(-dz, dx);
31
+ box.castShadow = true;
32
+ box.receiveShadow = true;
33
+ group.add(box);
34
+ }
35
+ group.name = `wall:${wall.id}`;
36
+ group.userData.scenographyId = wall.id;
37
+ return group;
38
+ }
39
+ //#endregion
40
+ //#region src/colliders.ts
41
+ /**
42
+ * Build a collision box for every wall segment (matching the built geometry) —
43
+ * the `colliders` capability the camera controls consume to keep the visitor
44
+ * out of walls.
45
+ */
46
+ function buildColliders(walls) {
47
+ const colliders = [];
48
+ for (const wall of walls) {
49
+ const halfThickness = (wall.thickness ?? 30) / 2;
50
+ const segments = segmentCount(wall);
51
+ for (let i = 0; i < segments; i++) {
52
+ const [a, b] = edgeEndpoints(wall, i);
53
+ const ex = b[0] - a[0];
54
+ const ez = b[1] - a[1];
55
+ const length = Math.hypot(ex, ez);
56
+ if (length === 0) continue;
57
+ colliders.push({
58
+ cx: (a[0] + b[0]) / 2,
59
+ cz: (a[1] + b[1]) / 2,
60
+ dx: ex / length,
61
+ dz: ez / length,
62
+ halfLength: length / 2,
63
+ halfThickness
64
+ });
65
+ }
66
+ }
67
+ return colliders;
68
+ }
69
+ //#endregion
70
+ //#region src/edgeIndex.ts
71
+ /**
72
+ * Build the `name -> EdgeAnchor` index across every wall — the `edges`
73
+ * capability the artwork plugin consumes to hang pieces on a named segment.
74
+ */
75
+ function indexEdges(walls) {
76
+ const index = /* @__PURE__ */ new Map();
77
+ for (const wall of walls) {
78
+ const height = wall.height ?? 300;
79
+ const thickness = wall.thickness ?? 30;
80
+ for (const edge of wall.edges ?? []) {
81
+ const [a, b] = edgeEndpoints(wall, edge.segment);
82
+ index.set(edge.name, {
83
+ a,
84
+ b,
85
+ height,
86
+ thickness
87
+ });
88
+ }
89
+ }
90
+ return index;
91
+ }
92
+ //#endregion
93
+ //#region src/plugin.ts
94
+ /**
95
+ * The wall plugin: extrudes each `Wall` into geometry, and — across all walls —
96
+ * publishes the `edges` capability (named anchors artworks hang on) and the
97
+ * `colliders` capability (obstacles the camera can't pass). A single change
98
+ * token covers both, so consumers only rebuild when the walls actually change.
99
+ */
100
+ const wall = () => ({
101
+ data: wallData,
102
+ build: (definition) => ({ object: buildWall(definition) }),
103
+ provide: (walls, ctx) => {
104
+ const token = JSON.stringify(walls);
105
+ ctx.capabilities.provide(EDGES, indexEdges(walls), token);
106
+ ctx.capabilities.provide(COLLIDERS, buildColliders(walls), token);
107
+ }
108
+ });
109
+ //#endregion
110
+ export { WALL_DEFAULT_HEIGHT, WALL_DEFAULT_THICKNESS, allEdgeNames, buildColliders, buildWall, indexEdges, validateWall, wall, wallData };
111
+
112
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/buildWall.ts","../src/colliders.ts","../src/edgeIndex.ts","../src/plugin.ts"],"sourcesContent":["import { applyTexture } from \"@scenography/engine\";\nimport { BoxGeometry, Group, Mesh, MeshStandardMaterial, type Object3D } from \"three\";\n\nimport {\n WALL_DEFAULT_HEIGHT,\n WALL_DEFAULT_THICKNESS,\n edgeEndpoints,\n segmentCount,\n type Wall,\n} from \"./data\";\n\n/**\n * Build the three.js object for a `Wall`: one thickened box per path segment,\n * following the polyline at its center. Corners are simple butt joints (boxes\n * overlap slightly), which reads cleanly at a uniform thickness.\n */\nexport function buildWall(wall: Wall): Object3D {\n const height = wall.height ?? WALL_DEFAULT_HEIGHT;\n const thickness = wall.thickness ?? WALL_DEFAULT_THICKNESS;\n const material = new MeshStandardMaterial({ color: wall.color ?? \"#ffffff\" });\n const needsUv1 = wall.texture ? applyTexture(material, wall.texture) : false;\n\n const group = new Group();\n const segments = segmentCount(wall);\n for (let i = 0; i < segments; i++) {\n const [a, b] = edgeEndpoints(wall, i);\n const dx = b[0] - a[0];\n const dz = b[1] - a[1];\n const length = Math.hypot(dx, dz);\n if (length === 0) continue;\n\n const geometry = new BoxGeometry(length, height, thickness);\n // aoMap reads the second UV set; box geometry only ships `uv`, so mirror it.\n if (needsUv1) {\n const uv = geometry.getAttribute(\"uv\");\n if (uv) geometry.setAttribute(\"uv1\", uv);\n }\n\n const box = new Mesh(geometry, material);\n box.position.set((a[0] + b[0]) / 2, height / 2, (a[1] + b[1]) / 2);\n // Align the box's local X (its length) with the segment direction.\n box.rotation.y = Math.atan2(-dz, dx);\n box.castShadow = true;\n box.receiveShadow = true;\n group.add(box);\n }\n\n group.name = `wall:${wall.id}`;\n group.userData.scenographyId = wall.id;\n return group;\n}\n","import type { Collider } from \"@scenography/engine\";\n\nimport { WALL_DEFAULT_THICKNESS, edgeEndpoints, segmentCount, type Wall } from \"./data\";\n\n/**\n * Build a collision box for every wall segment (matching the built geometry) —\n * the `colliders` capability the camera controls consume to keep the visitor\n * out of walls.\n */\nexport function buildColliders(walls: Wall[]): Collider[] {\n const colliders: Collider[] = [];\n for (const wall of walls) {\n const halfThickness = (wall.thickness ?? WALL_DEFAULT_THICKNESS) / 2;\n const segments = segmentCount(wall);\n for (let i = 0; i < segments; i++) {\n const [a, b] = edgeEndpoints(wall, i);\n const ex = b[0] - a[0];\n const ez = b[1] - a[1];\n const length = Math.hypot(ex, ez);\n if (length === 0) continue;\n colliders.push({\n cx: (a[0] + b[0]) / 2,\n cz: (a[1] + b[1]) / 2,\n dx: ex / length,\n dz: ez / length,\n halfLength: length / 2,\n halfThickness,\n });\n }\n }\n return colliders;\n}\n","import type { EdgeAnchor, EdgeIndex } from \"@scenography/engine\";\n\nimport {\n WALL_DEFAULT_HEIGHT,\n WALL_DEFAULT_THICKNESS,\n edgeEndpoints,\n type Wall,\n} from \"./data\";\n\n/**\n * Build the `name -> EdgeAnchor` index across every wall — the `edges`\n * capability the artwork plugin consumes to hang pieces on a named segment.\n */\nexport function indexEdges(walls: Wall[]): EdgeIndex {\n const index = new Map<string, EdgeAnchor>();\n for (const wall of walls) {\n const height = wall.height ?? WALL_DEFAULT_HEIGHT;\n const thickness = wall.thickness ?? WALL_DEFAULT_THICKNESS;\n for (const edge of wall.edges ?? []) {\n const [a, b] = edgeEndpoints(wall, edge.segment);\n index.set(edge.name, { a, b, height, thickness });\n }\n }\n return index;\n}\n","import { COLLIDERS, EDGES, type EnginePlugin } from \"@scenography/engine\";\n\nimport { buildWall } from \"./buildWall\";\nimport { buildColliders } from \"./colliders\";\nimport { wallData, type Wall } from \"./data\";\nimport { indexEdges } from \"./edgeIndex\";\n\n/**\n * The wall plugin: extrudes each `Wall` into geometry, and — across all walls —\n * publishes the `edges` capability (named anchors artworks hang on) and the\n * `colliders` capability (obstacles the camera can't pass). A single change\n * token covers both, so consumers only rebuild when the walls actually change.\n */\nexport const wall = (): EnginePlugin<Wall> => ({\n data: wallData,\n build: (definition) => ({ object: buildWall(definition) }),\n provide: (walls, ctx) => {\n const token = JSON.stringify(walls);\n ctx.capabilities.provide(EDGES, indexEdges(walls), token);\n ctx.capabilities.provide(COLLIDERS, buildColliders(walls), token);\n },\n});\n"],"mappings":";;;;;;;;;AAgBA,SAAgB,UAAU,MAAsB;CAC9C,MAAM,SAAS,KAAK,UAAA;CACpB,MAAM,YAAY,KAAK,aAAA;CACvB,MAAM,WAAW,IAAI,qBAAqB,EAAE,OAAO,KAAK,SAAS,UAAU,CAAC;CAC5E,MAAM,WAAW,KAAK,UAAU,aAAa,UAAU,KAAK,OAAO,IAAI;CAEvE,MAAM,QAAQ,IAAI,MAAM;CACxB,MAAM,WAAW,aAAa,IAAI;CAClC,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,KAAK;EACjC,MAAM,CAAC,GAAG,KAAK,cAAc,MAAM,CAAC;EACpC,MAAM,KAAK,EAAE,KAAK,EAAE;EACpB,MAAM,KAAK,EAAE,KAAK,EAAE;EACpB,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;EAChC,IAAI,WAAW,GAAG;EAElB,MAAM,WAAW,IAAI,YAAY,QAAQ,QAAQ,SAAS;EAE1D,IAAI,UAAU;GACZ,MAAM,KAAK,SAAS,aAAa,IAAI;GACrC,IAAI,IAAI,SAAS,aAAa,OAAO,EAAE;EACzC;EAEA,MAAM,MAAM,IAAI,KAAK,UAAU,QAAQ;EACvC,IAAI,SAAS,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC;EAEjE,IAAI,SAAS,IAAI,KAAK,MAAM,CAAC,IAAI,EAAE;EACnC,IAAI,aAAa;EACjB,IAAI,gBAAgB;EACpB,MAAM,IAAI,GAAG;CACf;CAEA,MAAM,OAAO,QAAQ,KAAK;CAC1B,MAAM,SAAS,gBAAgB,KAAK;CACpC,OAAO;AACT;;;;;;;;ACzCA,SAAgB,eAAe,OAA2B;CACxD,MAAM,YAAwB,CAAC;CAC/B,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,iBAAiB,KAAK,aAAA,MAAuC;EACnE,MAAM,WAAW,aAAa,IAAI;EAClC,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,KAAK;GACjC,MAAM,CAAC,GAAG,KAAK,cAAc,MAAM,CAAC;GACpC,MAAM,KAAK,EAAE,KAAK,EAAE;GACpB,MAAM,KAAK,EAAE,KAAK,EAAE;GACpB,MAAM,SAAS,KAAK,MAAM,IAAI,EAAE;GAChC,IAAI,WAAW,GAAG;GAClB,UAAU,KAAK;IACb,KAAK,EAAE,KAAK,EAAE,MAAM;IACpB,KAAK,EAAE,KAAK,EAAE,MAAM;IACpB,IAAI,KAAK;IACT,IAAI,KAAK;IACT,YAAY,SAAS;IACrB;GACF,CAAC;EACH;CACF;CACA,OAAO;AACT;;;;;;;AClBA,SAAgB,WAAW,OAA0B;CACnD,MAAM,wBAAQ,IAAI,IAAwB;CAC1C,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,KAAK,UAAA;EACpB,MAAM,YAAY,KAAK,aAAA;EACvB,KAAK,MAAM,QAAQ,KAAK,SAAS,CAAC,GAAG;GACnC,MAAM,CAAC,GAAG,KAAK,cAAc,MAAM,KAAK,OAAO;GAC/C,MAAM,IAAI,KAAK,MAAM;IAAE;IAAG;IAAG;IAAQ;GAAU,CAAC;EAClD;CACF;CACA,OAAO;AACT;;;;;;;;;ACXA,MAAa,cAAkC;CAC7C,MAAM;CACN,QAAQ,gBAAgB,EAAE,QAAQ,UAAU,UAAU,EAAE;CACxD,UAAU,OAAO,QAAQ;EACvB,MAAM,QAAQ,KAAK,UAAU,KAAK;EAClC,IAAI,aAAa,QAAQ,OAAO,WAAW,KAAK,GAAG,KAAK;EACxD,IAAI,aAAa,QAAQ,WAAW,eAAe,KAAK,GAAG,KAAK;CAClE;AACF"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@scenography/plugin-wall",
3
+ "version": "0.2.0",
4
+ "description": "Wall plugin for scenography: extrudes a top-down path into walls, publishes named edges (for hanging art) and colliders. Data half importable without three.js.",
5
+ "license": "MIT",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "main": "./dist/index.cjs",
11
+ "module": "./dist/index.mjs",
12
+ "types": "./dist/index.d.cts",
13
+ "exports": {
14
+ ".": {
15
+ "import": {
16
+ "types": "./dist/index.d.mts",
17
+ "default": "./dist/index.mjs"
18
+ },
19
+ "require": {
20
+ "types": "./dist/index.d.cts",
21
+ "default": "./dist/index.cjs"
22
+ }
23
+ },
24
+ "./data": {
25
+ "import": {
26
+ "types": "./dist/data.d.mts",
27
+ "default": "./dist/data.mjs"
28
+ },
29
+ "require": {
30
+ "types": "./dist/data.d.cts",
31
+ "default": "./dist/data.cjs"
32
+ }
33
+ }
34
+ },
35
+ "dependencies": {
36
+ "@scenography/scene": "0.2.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/three": "^0.160.0",
40
+ "three": "^0.160.0",
41
+ "@scenography/engine": "0.2.0"
42
+ },
43
+ "peerDependencies": {
44
+ "three": ">=0.160.0",
45
+ "@scenography/engine": "0.2.0"
46
+ },
47
+ "scripts": {
48
+ "build": "tsdown",
49
+ "dev": "tsdown --watch",
50
+ "typecheck": "tsc --noEmit"
51
+ }
52
+ }