@react-arch/renderer-2d 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 React Arch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,67 @@
1
+ import { BuildingDocument, EntityRef } from "@react-arch/core";
2
+ import { Vec2 } from "@react-arch/geometry";
3
+
4
+ //#region src/Plan2D.d.ts
5
+ interface Plan2DProps {
6
+ document: BuildingDocument;
7
+ floorIds: string[] | "all";
8
+ ghostFloorIds?: string[];
9
+ selected?: EntityRef | null;
10
+ onSelect?: (ref: EntityRef | null) => void;
11
+ onHover?: (ref: EntityRef | null) => void;
12
+ showGrid?: boolean;
13
+ showMeasurements?: boolean;
14
+ gridSize?: number;
15
+ className?: string;
16
+ }
17
+ declare function Plan2D(props: Plan2DProps): import("react").JSX.Element;
18
+ //#endregion
19
+ //#region src/scene.d.ts
20
+ interface WallDraw {
21
+ id: string;
22
+ floorId: string;
23
+ polygon: Vec2[];
24
+ start: Vec2;
25
+ end: Vec2;
26
+ thickness: number;
27
+ }
28
+ interface RoomDraw {
29
+ id: string;
30
+ floorId: string;
31
+ name: string;
32
+ polygon: Vec2[];
33
+ centroid: Vec2;
34
+ area: number;
35
+ }
36
+ interface OpeningDraw {
37
+ id: string;
38
+ floorId: string;
39
+ type: "door" | "window" | "opening";
40
+ p0: Vec2;
41
+ p1: Vec2;
42
+ center: Vec2;
43
+ dir: Vec2;
44
+ normal: Vec2;
45
+ width: number;
46
+ }
47
+ interface ObjectDraw {
48
+ id: string;
49
+ floorId: string;
50
+ type: string;
51
+ center: Vec2;
52
+ /** Footprint size [width(X), depth(Y)] in metres. */
53
+ size: [number, number];
54
+ rotation: number;
55
+ }
56
+ interface PlanScene {
57
+ walls: WallDraw[];
58
+ rooms: RoomDraw[];
59
+ openings: OpeningDraw[];
60
+ objects: ObjectDraw[];
61
+ }
62
+ declare function buildPlanScene(doc: BuildingDocument, floorIds: string[] | "all"): PlanScene;
63
+ /** Hit-test a world point against the scene, nearest-first by entity type. */
64
+ declare function hitTest(scene: PlanScene, point: Vec2, tolerance: number): EntityRef | null;
65
+ //#endregion
66
+ export { Plan2D, type Plan2DProps, type PlanScene, buildPlanScene, hitTest };
67
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/Plan2D.tsx","../src/scene.ts"],"mappings":";;;;UAMiB,WAAA;EACf,QAAA,EAAU,gBAAA;EACV,QAAA;EACA,aAAA;EACA,QAAA,GAAW,SAAA;EACX,QAAA,IAAY,GAAA,EAAK,SAAA;EACjB,OAAA,IAAW,GAAA,EAAK,SAAA;EAChB,QAAA;EACA,gBAAA;EACA,QAAA;EACA,SAAA;AAAA;AAAA,iBAyBc,MAAA,CAAO,KAAA,EAAO,WAAW,mBAAA,GAAA,CAAA,OAAA;;;UC1BxB,QAAA;EACf,EAAA;EACA,OAAA;EACA,OAAA,EAAS,IAAA;EACT,KAAA,EAAO,IAAA;EACP,GAAA,EAAK,IAAA;EACL,SAAA;AAAA;AAAA,UAEe,QAAA;EACf,EAAA;EACA,OAAA;EACA,IAAA;EACA,OAAA,EAAS,IAAA;EACT,QAAA,EAAU,IAAI;EACd,IAAA;AAAA;AAAA,UAEe,WAAA;EACf,EAAA;EACA,OAAA;EACA,IAAA;EACA,EAAA,EAAI,IAAA;EACJ,EAAA,EAAI,IAAA;EACJ,MAAA,EAAQ,IAAA;EACR,GAAA,EAAK,IAAA;EACL,MAAA,EAAQ,IAAA;EACR,KAAA;AAAA;AAAA,UAEe,UAAA;EACf,EAAA;EACA,OAAA;EACA,IAAA;EACA,MAAA,EAAQ,IAAI;EDL2B;ECOvC,IAAA;EACA,QAAA;AAAA;AAAA,UAEe,SAAA;EACf,KAAA,EAAO,QAAA;EACP,KAAA,EAAO,QAAA;EACP,QAAA,EAAU,WAAA;EACV,OAAA,EAAS,UAAA;AAAA;AAAA,iBASK,cAAA,CACd,GAAA,EAAK,gBAAA,EACL,QAAA,qBACC,SAAS;;iBAyDI,OAAA,CACd,KAAA,EAAO,SAAA,EACP,KAAA,EAAO,IAAA,EACP,SAAA,WACC,SAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,437 @@
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { allFloors, furnitureColor, furnitureDims } from "@react-arch/core";
3
+ import { bounds, distance, normal, openingSpan, pointInPolygon, polygonArea, polygonCentroid, projectPointOnSegment, wallDirection, wallPolygon } from "@react-arch/geometry";
4
+ import { jsx } from "react/jsx-runtime";
5
+ //#region src/scene.ts
6
+ function visibleFloors(doc, floorIds) {
7
+ const floors = allFloors(doc);
8
+ if (floorIds === "all") return floors.filter((f) => f.visible);
9
+ return floors.filter((f) => floorIds.includes(f.id));
10
+ }
11
+ function buildPlanScene(doc, floorIds) {
12
+ const scene = {
13
+ walls: [],
14
+ rooms: [],
15
+ openings: [],
16
+ objects: []
17
+ };
18
+ for (const floor of visibleFloors(doc, floorIds)) {
19
+ for (const r of floor.rooms) scene.rooms.push({
20
+ id: r.id,
21
+ floorId: floor.id,
22
+ name: r.name,
23
+ polygon: r.polygon,
24
+ centroid: polygonCentroid(r.polygon),
25
+ area: polygonArea(r.polygon)
26
+ });
27
+ const wallById = new Map(floor.walls.map((w) => [w.id, w]));
28
+ for (const w of floor.walls) scene.walls.push({
29
+ id: w.id,
30
+ floorId: floor.id,
31
+ polygon: wallPolygon(w, w.thickness / 2),
32
+ start: w.start,
33
+ end: w.end,
34
+ thickness: w.thickness
35
+ });
36
+ for (const o of floor.openings) {
37
+ const wall = wallById.get(o.wallId);
38
+ if (!wall) continue;
39
+ const span = openingSpan(wall, o);
40
+ const dir = wallDirection(wall);
41
+ scene.openings.push({
42
+ id: o.id,
43
+ floorId: floor.id,
44
+ type: o.type,
45
+ p0: span.start,
46
+ p1: span.end,
47
+ center: span.center,
48
+ dir,
49
+ normal: normal(dir),
50
+ width: o.width
51
+ });
52
+ }
53
+ for (const ob of floor.objects) {
54
+ const d = furnitureDims(ob.type, ob.scale);
55
+ scene.objects.push({
56
+ id: ob.id,
57
+ floorId: floor.id,
58
+ type: ob.type,
59
+ center: [ob.position[0], ob.position[1]],
60
+ size: [d.width, d.depth],
61
+ rotation: ob.rotation[2]
62
+ });
63
+ }
64
+ }
65
+ return scene;
66
+ }
67
+ /** Hit-test a world point against the scene, nearest-first by entity type. */
68
+ function hitTest(scene, point, tolerance) {
69
+ for (const o of scene.openings) if (distance(point, o.center) <= Math.max(tolerance, o.width / 2)) return {
70
+ kind: "opening",
71
+ id: o.id
72
+ };
73
+ for (const ob of scene.objects) {
74
+ const dx = Math.abs(point[0] - ob.center[0]);
75
+ const dy = Math.abs(point[1] - ob.center[1]);
76
+ if (dx <= ob.size[0] / 2 + tolerance && dy <= ob.size[1] / 2 + tolerance) return {
77
+ kind: "object",
78
+ id: ob.id
79
+ };
80
+ }
81
+ for (const w of scene.walls) if (projectPointOnSegment(point, w.start, w.end).distance <= w.thickness / 2 + tolerance) return {
82
+ kind: "wall",
83
+ id: w.id
84
+ };
85
+ for (const r of scene.rooms) if (pointInPolygon(point, r.polygon)) return {
86
+ kind: "room",
87
+ id: r.id
88
+ };
89
+ return null;
90
+ }
91
+ //#endregion
92
+ //#region src/Plan2D.tsx
93
+ const COLORS = {
94
+ bg: "#15171c",
95
+ grid: "#23262e",
96
+ gridMajor: "#2c3038",
97
+ room: "rgba(76, 142, 255, 0.07)",
98
+ roomLabel: "#9aa3b2",
99
+ wall: "#c9ccd1",
100
+ wallStroke: "#080a0d",
101
+ opening: "#e8e6e1",
102
+ door: "#5fb3c9",
103
+ window: "#5f9bff",
104
+ select: "#4c8eff",
105
+ ghost: "rgba(120,130,150,0.25)",
106
+ dim: "#6f7787"
107
+ };
108
+ function Plan2D(props) {
109
+ const { document: doc, floorIds, showGrid = true, showMeasurements = true, gridSize = .5 } = props;
110
+ const canvasRef = useRef(null);
111
+ const containerRef = useRef(null);
112
+ const [vp, setVp] = useState({
113
+ scale: 40,
114
+ ox: 0,
115
+ oy: 0
116
+ });
117
+ const [size, setSize] = useState({
118
+ w: 0,
119
+ h: 0
120
+ });
121
+ const dragRef = useRef(null);
122
+ const fittedKey = useRef("");
123
+ const scene = useMemo(() => buildPlanScene(doc, floorIds), [doc, floorIds]);
124
+ const ghostScene = useMemo(() => props.ghostFloorIds?.length ? buildPlanScene(doc, props.ghostFloorIds) : null, [doc, props.ghostFloorIds]);
125
+ const toScreen = useCallback((p) => [p[0] * vp.scale + vp.ox, p[1] * vp.scale + vp.oy], [vp]);
126
+ const toWorld = useCallback((sx, sy) => [(sx - vp.ox) / vp.scale, (sy - vp.oy) / vp.scale], [vp]);
127
+ useEffect(() => {
128
+ const el = containerRef.current;
129
+ if (!el) return;
130
+ const ro = new ResizeObserver(() => {
131
+ setSize({
132
+ w: el.clientWidth,
133
+ h: el.clientHeight
134
+ });
135
+ });
136
+ ro.observe(el);
137
+ setSize({
138
+ w: el.clientWidth,
139
+ h: el.clientHeight
140
+ });
141
+ return () => ro.disconnect();
142
+ }, []);
143
+ const fit = useCallback(() => {
144
+ if (size.w === 0 || size.h === 0) return;
145
+ const pts = [];
146
+ for (const w of scene.walls) pts.push(...w.polygon);
147
+ for (const r of scene.rooms) pts.push(...r.polygon);
148
+ if (pts.length === 0) {
149
+ setVp({
150
+ scale: 40,
151
+ ox: size.w / 2,
152
+ oy: size.h / 2
153
+ });
154
+ return;
155
+ }
156
+ const b = bounds(pts);
157
+ const margin = 60;
158
+ const scale = Math.min((size.w - margin * 2) / Math.max(b.width, .5), (size.h - margin * 2) / Math.max(b.height, .5));
159
+ const cx = (b.min[0] + b.max[0]) / 2;
160
+ const cy = (b.min[1] + b.max[1]) / 2;
161
+ setVp({
162
+ scale,
163
+ ox: size.w / 2 - cx * scale,
164
+ oy: size.h / 2 - cy * scale
165
+ });
166
+ }, [scene, size]);
167
+ useEffect(() => {
168
+ const key = `${doc.id}:${Array.isArray(floorIds) ? floorIds.join(",") : "all"}:${size.w}x${size.h}`;
169
+ if (size.w > 0 && fittedKey.current !== key) {
170
+ fittedKey.current = key;
171
+ fit();
172
+ }
173
+ }, [
174
+ doc.id,
175
+ floorIds,
176
+ size,
177
+ fit
178
+ ]);
179
+ useEffect(() => {
180
+ const canvas = canvasRef.current;
181
+ if (!canvas || size.w === 0) return;
182
+ const dpr = window.devicePixelRatio || 1;
183
+ canvas.width = size.w * dpr;
184
+ canvas.height = size.h * dpr;
185
+ const ctx = canvas.getContext("2d");
186
+ if (!ctx) return;
187
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
188
+ draw(ctx, size, vp, scene, ghostScene, props.selected ?? null, {
189
+ showGrid,
190
+ showMeasurements,
191
+ gridSize,
192
+ toScreen
193
+ });
194
+ }, [
195
+ size,
196
+ vp,
197
+ scene,
198
+ ghostScene,
199
+ props.selected,
200
+ showGrid,
201
+ showMeasurements,
202
+ gridSize,
203
+ toScreen
204
+ ]);
205
+ const onPointerDown = (e) => {
206
+ e.target.setPointerCapture?.(e.pointerId);
207
+ dragRef.current = {
208
+ x: e.clientX,
209
+ y: e.clientY,
210
+ moved: false,
211
+ ox: vp.ox,
212
+ oy: vp.oy
213
+ };
214
+ };
215
+ const onPointerMove = (e) => {
216
+ const d = dragRef.current;
217
+ const rect = canvasRef.current.getBoundingClientRect();
218
+ if (d) {
219
+ const dx = e.clientX - d.x;
220
+ const dy = e.clientY - d.y;
221
+ if (Math.abs(dx) + Math.abs(dy) > 3) d.moved = true;
222
+ if (d.moved) setVp((v) => ({
223
+ ...v,
224
+ ox: d.ox + dx,
225
+ oy: d.oy + dy
226
+ }));
227
+ } else if (props.onHover) {
228
+ const world = toWorld(e.clientX - rect.left, e.clientY - rect.top);
229
+ props.onHover(hitTest(scene, world, 6 / vp.scale));
230
+ }
231
+ };
232
+ const onPointerUp = (e) => {
233
+ const d = dragRef.current;
234
+ dragRef.current = null;
235
+ if (d && !d.moved && props.onSelect) {
236
+ const rect = canvasRef.current.getBoundingClientRect();
237
+ const world = toWorld(e.clientX - rect.left, e.clientY - rect.top);
238
+ props.onSelect(hitTest(scene, world, 6 / vp.scale));
239
+ }
240
+ };
241
+ const onWheel = (e) => {
242
+ const rect = canvasRef.current.getBoundingClientRect();
243
+ const sx = e.clientX - rect.left;
244
+ const sy = e.clientY - rect.top;
245
+ const factor = Math.exp(-e.deltaY * .0015);
246
+ setVp((v) => {
247
+ const scale = Math.max(4, Math.min(400, v.scale * factor));
248
+ const wx = (sx - v.ox) / v.scale;
249
+ const wy = (sy - v.oy) / v.scale;
250
+ return {
251
+ scale,
252
+ ox: sx - wx * scale,
253
+ oy: sy - wy * scale
254
+ };
255
+ });
256
+ };
257
+ return /* @__PURE__ */ jsx("div", {
258
+ ref: containerRef,
259
+ className: props.className,
260
+ style: {
261
+ width: "100%",
262
+ height: "100%",
263
+ position: "relative",
264
+ background: COLORS.bg
265
+ },
266
+ children: /* @__PURE__ */ jsx("canvas", {
267
+ ref: canvasRef,
268
+ style: {
269
+ width: "100%",
270
+ height: "100%",
271
+ display: "block",
272
+ cursor: dragRef.current?.moved ? "grabbing" : "default",
273
+ touchAction: "none"
274
+ },
275
+ onPointerDown,
276
+ onPointerMove,
277
+ onPointerUp,
278
+ onPointerLeave: () => props.onHover?.(null),
279
+ onWheel,
280
+ onDoubleClick: fit
281
+ })
282
+ });
283
+ }
284
+ function draw(ctx, size, vp, scene, ghost, selected, opts) {
285
+ const { toScreen } = opts;
286
+ ctx.fillStyle = COLORS.bg;
287
+ ctx.fillRect(0, 0, size.w, size.h);
288
+ if (opts.showGrid) drawGrid(ctx, size, vp, opts.gridSize);
289
+ if (ghost) {
290
+ ctx.save();
291
+ ctx.globalAlpha = .4;
292
+ for (const w of ghost.walls) fillPolygon(ctx, w.polygon, COLORS.ghost, toScreen);
293
+ ctx.restore();
294
+ }
295
+ for (const r of scene.rooms) fillPolygon(ctx, r.polygon, COLORS.room, toScreen);
296
+ for (const w of scene.walls) {
297
+ const isSel = selected?.kind === "wall" && selected.id === w.id;
298
+ fillPolygon(ctx, w.polygon, isSel ? COLORS.select : COLORS.wall, toScreen, COLORS.wallStroke);
299
+ }
300
+ for (const ob of scene.objects) drawFurniture(ctx, ob, vp, toScreen, selected?.kind === "object" && selected.id === ob.id);
301
+ for (const o of scene.openings) drawOpening(ctx, o, toScreen, selected?.kind === "opening" && selected.id === o.id);
302
+ for (const r of scene.rooms) {
303
+ const c = toScreen(r.centroid);
304
+ if (selected?.kind === "room" && selected.id === r.id) {
305
+ ctx.save();
306
+ ctx.beginPath();
307
+ ctx.strokeStyle = COLORS.select;
308
+ ctx.lineWidth = 2;
309
+ polyPath(ctx, r.polygon, toScreen);
310
+ ctx.stroke();
311
+ ctx.restore();
312
+ }
313
+ ctx.fillStyle = COLORS.roomLabel;
314
+ ctx.font = "600 12px ui-sans-serif, system-ui, sans-serif";
315
+ ctx.textAlign = "center";
316
+ ctx.fillText(r.name, c[0], c[1] - 2);
317
+ if (opts.showMeasurements) {
318
+ ctx.fillStyle = COLORS.dim;
319
+ ctx.font = "11px ui-monospace, monospace";
320
+ ctx.fillText(`${r.area.toFixed(1)} m²`, c[0], c[1] + 13);
321
+ }
322
+ }
323
+ }
324
+ function drawGrid(ctx, size, vp, gridSize) {
325
+ const step = gridSize * vp.scale;
326
+ if (step < 6) return;
327
+ const startX = vp.ox % step;
328
+ const startY = vp.oy % step;
329
+ ctx.lineWidth = 1;
330
+ for (let x = startX; x < size.w; x += step) {
331
+ ctx.strokeStyle = COLORS.grid;
332
+ ctx.beginPath();
333
+ ctx.moveTo(x, 0);
334
+ ctx.lineTo(x, size.h);
335
+ ctx.stroke();
336
+ }
337
+ for (let y = startY; y < size.h; y += step) {
338
+ ctx.strokeStyle = COLORS.grid;
339
+ ctx.beginPath();
340
+ ctx.moveTo(0, y);
341
+ ctx.lineTo(size.w, y);
342
+ ctx.stroke();
343
+ }
344
+ }
345
+ function polyPath(ctx, poly, toScreen) {
346
+ poly.forEach((p, i) => {
347
+ const s = toScreen(p);
348
+ if (i === 0) ctx.moveTo(s[0], s[1]);
349
+ else ctx.lineTo(s[0], s[1]);
350
+ });
351
+ ctx.closePath();
352
+ }
353
+ function fillPolygon(ctx, poly, fill, toScreen, stroke) {
354
+ ctx.beginPath();
355
+ polyPath(ctx, poly, toScreen);
356
+ ctx.fillStyle = fill;
357
+ ctx.fill();
358
+ if (stroke) {
359
+ ctx.lineWidth = 1;
360
+ ctx.strokeStyle = stroke;
361
+ ctx.stroke();
362
+ }
363
+ }
364
+ function drawFurniture(ctx, ob, vp, toScreen, selected) {
365
+ const c = toScreen(ob.center);
366
+ const w = ob.size[0] * vp.scale;
367
+ const h = ob.size[1] * vp.scale;
368
+ ctx.save();
369
+ ctx.translate(c[0], c[1]);
370
+ if (ob.rotation) ctx.rotate(ob.rotation);
371
+ const color = selected ? COLORS.select : furnitureColor(ob.type);
372
+ ctx.fillStyle = color + "33";
373
+ ctx.strokeStyle = color;
374
+ ctx.lineWidth = selected ? 2 : 1.25;
375
+ const r = Math.min(4, w / 4, h / 4);
376
+ roundRect(ctx, -w / 2, -h / 2, w, h, r);
377
+ ctx.fill();
378
+ ctx.stroke();
379
+ if (Math.min(w, h) > 26) {
380
+ ctx.fillStyle = COLORS.roomLabel;
381
+ ctx.font = "9px ui-sans-serif, system-ui, sans-serif";
382
+ ctx.textAlign = "center";
383
+ ctx.textBaseline = "middle";
384
+ ctx.fillText(ob.type, 0, 0);
385
+ }
386
+ ctx.restore();
387
+ }
388
+ function roundRect(ctx, x, y, w, h, r) {
389
+ ctx.beginPath();
390
+ ctx.moveTo(x + r, y);
391
+ ctx.arcTo(x + w, y, x + w, y + h, r);
392
+ ctx.arcTo(x + w, y + h, x, y + h, r);
393
+ ctx.arcTo(x, y + h, x, y, r);
394
+ ctx.arcTo(x, y, x + w, y, r);
395
+ ctx.closePath();
396
+ }
397
+ function drawOpening(ctx, o, toScreen, selected) {
398
+ const a = toScreen(o.p0);
399
+ const b = toScreen(o.p1);
400
+ const color = selected ? COLORS.select : o.type === "door" ? COLORS.door : o.type === "window" ? COLORS.window : COLORS.opening;
401
+ ctx.strokeStyle = color;
402
+ ctx.lineWidth = selected ? 3 : 2;
403
+ ctx.save();
404
+ ctx.strokeStyle = COLORS.bg;
405
+ ctx.lineWidth = 6;
406
+ ctx.beginPath();
407
+ ctx.moveTo(a[0], a[1]);
408
+ ctx.lineTo(b[0], b[1]);
409
+ ctx.stroke();
410
+ ctx.restore();
411
+ if (o.type === "door") {
412
+ const cx = a[0];
413
+ const cy = a[1];
414
+ const r = Math.hypot(b[0] - a[0], b[1] - a[1]);
415
+ const start = Math.atan2(b[1] - a[1], b[0] - a[0]);
416
+ ctx.strokeStyle = color;
417
+ ctx.lineWidth = 1.5;
418
+ ctx.beginPath();
419
+ ctx.arc(cx, cy, r, start, start + Math.PI / 2);
420
+ ctx.stroke();
421
+ ctx.beginPath();
422
+ ctx.moveTo(a[0], a[1]);
423
+ ctx.lineTo(b[0], b[1]);
424
+ ctx.stroke();
425
+ } else {
426
+ ctx.strokeStyle = color;
427
+ ctx.lineWidth = selected ? 3 : 2;
428
+ ctx.beginPath();
429
+ ctx.moveTo(a[0], a[1]);
430
+ ctx.lineTo(b[0], b[1]);
431
+ ctx.stroke();
432
+ }
433
+ }
434
+ //#endregion
435
+ export { Plan2D, buildPlanScene, hitTest };
436
+
437
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/scene.ts","../src/Plan2D.tsx"],"sourcesContent":["import type { BuildingDocument, EntityRef, Floor } from \"@react-arch/core\";\nimport { allFloors, furnitureDims } from \"@react-arch/core\";\nimport {\n type Vec2,\n distance,\n normal,\n openingSpan,\n pointInPolygon,\n polygonArea,\n polygonCentroid,\n projectPointOnSegment,\n wallDirection,\n wallPolygon,\n} from \"@react-arch/geometry\";\n\nexport interface WallDraw {\n id: string;\n floorId: string;\n polygon: Vec2[];\n start: Vec2;\n end: Vec2;\n thickness: number;\n}\nexport interface RoomDraw {\n id: string;\n floorId: string;\n name: string;\n polygon: Vec2[];\n centroid: Vec2;\n area: number;\n}\nexport interface OpeningDraw {\n id: string;\n floorId: string;\n type: \"door\" | \"window\" | \"opening\";\n p0: Vec2;\n p1: Vec2;\n center: Vec2;\n dir: Vec2;\n normal: Vec2;\n width: number;\n}\nexport interface ObjectDraw {\n id: string;\n floorId: string;\n type: string;\n center: Vec2;\n /** Footprint size [width(X), depth(Y)] in metres. */\n size: [number, number];\n rotation: number;\n}\nexport interface PlanScene {\n walls: WallDraw[];\n rooms: RoomDraw[];\n openings: OpeningDraw[];\n objects: ObjectDraw[];\n}\n\nfunction visibleFloors(doc: BuildingDocument, floorIds: string[] | \"all\"): Floor[] {\n const floors = allFloors(doc);\n if (floorIds === \"all\") return floors.filter((f) => f.visible);\n return floors.filter((f) => floorIds.includes(f.id));\n}\n\nexport function buildPlanScene(\n doc: BuildingDocument,\n floorIds: string[] | \"all\",\n): PlanScene {\n const scene: PlanScene = { walls: [], rooms: [], openings: [], objects: [] };\n for (const floor of visibleFloors(doc, floorIds)) {\n for (const r of floor.rooms) {\n scene.rooms.push({\n id: r.id,\n floorId: floor.id,\n name: r.name,\n polygon: r.polygon,\n centroid: polygonCentroid(r.polygon),\n area: polygonArea(r.polygon),\n });\n }\n const wallById = new Map(floor.walls.map((w) => [w.id, w]));\n for (const w of floor.walls) {\n scene.walls.push({\n id: w.id,\n floorId: floor.id,\n polygon: wallPolygon(w, w.thickness / 2),\n start: w.start,\n end: w.end,\n thickness: w.thickness,\n });\n }\n for (const o of floor.openings) {\n const wall = wallById.get(o.wallId);\n if (!wall) continue;\n const span = openingSpan(wall, o);\n const dir = wallDirection(wall);\n scene.openings.push({\n id: o.id,\n floorId: floor.id,\n type: o.type,\n p0: span.start,\n p1: span.end,\n center: span.center,\n dir,\n normal: normal(dir),\n width: o.width,\n });\n }\n for (const ob of floor.objects) {\n const d = furnitureDims(ob.type, ob.scale);\n scene.objects.push({\n id: ob.id,\n floorId: floor.id,\n type: ob.type,\n center: [ob.position[0], ob.position[1]],\n size: [d.width, d.depth],\n rotation: ob.rotation[2],\n });\n }\n }\n return scene;\n}\n\n/** Hit-test a world point against the scene, nearest-first by entity type. */\nexport function hitTest(\n scene: PlanScene,\n point: Vec2,\n tolerance: number,\n): EntityRef | null {\n // Openings first (smallest), then furniture, then walls, then rooms.\n for (const o of scene.openings) {\n if (distance(point, o.center) <= Math.max(tolerance, o.width / 2)) {\n return { kind: \"opening\", id: o.id };\n }\n }\n for (const ob of scene.objects) {\n const dx = Math.abs(point[0] - ob.center[0]);\n const dy = Math.abs(point[1] - ob.center[1]);\n if (dx <= ob.size[0] / 2 + tolerance && dy <= ob.size[1] / 2 + tolerance) {\n return { kind: \"object\", id: ob.id };\n }\n }\n for (const w of scene.walls) {\n const proj = projectPointOnSegment(point, w.start, w.end);\n if (proj.distance <= w.thickness / 2 + tolerance) {\n return { kind: \"wall\", id: w.id };\n }\n }\n for (const r of scene.rooms) {\n if (pointInPolygon(point, r.polygon)) {\n return { kind: \"room\", id: r.id };\n }\n }\n return null;\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { BuildingDocument, EntityRef } from \"@react-arch/core\";\nimport { furnitureColor } from \"@react-arch/core\";\nimport { bounds, type Vec2 } from \"@react-arch/geometry\";\nimport { buildPlanScene, hitTest, type PlanScene } from \"./scene.js\";\n\nexport interface Plan2DProps {\n document: BuildingDocument;\n floorIds: string[] | \"all\";\n ghostFloorIds?: string[];\n selected?: EntityRef | null;\n onSelect?: (ref: EntityRef | null) => void;\n onHover?: (ref: EntityRef | null) => void;\n showGrid?: boolean;\n showMeasurements?: boolean;\n gridSize?: number;\n className?: string;\n}\n\ninterface Viewport {\n scale: number;\n ox: number;\n oy: number;\n}\n\nconst COLORS = {\n bg: \"#15171c\",\n grid: \"#23262e\",\n gridMajor: \"#2c3038\",\n room: \"rgba(76, 142, 255, 0.07)\",\n roomLabel: \"#9aa3b2\",\n wall: \"#c9ccd1\",\n wallStroke: \"#080a0d\",\n opening: \"#e8e6e1\",\n door: \"#5fb3c9\",\n window: \"#5f9bff\",\n select: \"#4c8eff\",\n ghost: \"rgba(120,130,150,0.25)\",\n dim: \"#6f7787\",\n};\n\nexport function Plan2D(props: Plan2DProps) {\n const { document: doc, floorIds, showGrid = true, showMeasurements = true, gridSize = 0.5 } = props;\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const containerRef = useRef<HTMLDivElement | null>(null);\n const [vp, setVp] = useState<Viewport>({ scale: 40, ox: 0, oy: 0 });\n const [size, setSize] = useState({ w: 0, h: 0 });\n const dragRef = useRef<{ x: number; y: number; moved: boolean; ox: number; oy: number } | null>(null);\n const fittedKey = useRef<string>(\"\");\n\n const scene = useMemo(() => buildPlanScene(doc, floorIds), [doc, floorIds]);\n const ghostScene = useMemo(\n () => (props.ghostFloorIds?.length ? buildPlanScene(doc, props.ghostFloorIds) : null),\n [doc, props.ghostFloorIds],\n );\n\n const toScreen = useCallback(\n (p: Vec2): Vec2 => [p[0] * vp.scale + vp.ox, p[1] * vp.scale + vp.oy],\n [vp],\n );\n const toWorld = useCallback(\n (sx: number, sy: number): Vec2 => [(sx - vp.ox) / vp.scale, (sy - vp.oy) / vp.scale],\n [vp],\n );\n\n // Resize observer for crisp HiDPI canvas.\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const ro = new ResizeObserver(() => {\n setSize({ w: el.clientWidth, h: el.clientHeight });\n });\n ro.observe(el);\n setSize({ w: el.clientWidth, h: el.clientHeight });\n return () => ro.disconnect();\n }, []);\n\n const fit = useCallback(() => {\n if (size.w === 0 || size.h === 0) return;\n const pts: Vec2[] = [];\n for (const w of scene.walls) pts.push(...w.polygon);\n for (const r of scene.rooms) pts.push(...r.polygon);\n if (pts.length === 0) {\n setVp({ scale: 40, ox: size.w / 2, oy: size.h / 2 });\n return;\n }\n const b = bounds(pts);\n const margin = 60;\n const scale = Math.min(\n (size.w - margin * 2) / Math.max(b.width, 0.5),\n (size.h - margin * 2) / Math.max(b.height, 0.5),\n );\n const cx = (b.min[0] + b.max[0]) / 2;\n const cy = (b.min[1] + b.max[1]) / 2;\n setVp({ scale, ox: size.w / 2 - cx * scale, oy: size.h / 2 - cy * scale });\n }, [scene, size]);\n\n // Auto-fit when the building or visible floors change (and on first layout).\n useEffect(() => {\n const key = `${doc.id}:${Array.isArray(floorIds) ? floorIds.join(\",\") : \"all\"}:${size.w}x${size.h}`;\n if (size.w > 0 && fittedKey.current !== key) {\n fittedKey.current = key;\n fit();\n }\n }, [doc.id, floorIds, size, fit]);\n\n // --- drawing -------------------------------------------------------------\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || size.w === 0) return;\n const dpr = window.devicePixelRatio || 1;\n canvas.width = size.w * dpr;\n canvas.height = size.h * dpr;\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n draw(ctx, size, vp, scene, ghostScene, props.selected ?? null, { showGrid, showMeasurements, gridSize, toScreen });\n }, [size, vp, scene, ghostScene, props.selected, showGrid, showMeasurements, gridSize, toScreen]);\n\n // --- interaction ---------------------------------------------------------\n const onPointerDown = (e: React.PointerEvent) => {\n (e.target as Element).setPointerCapture?.(e.pointerId);\n dragRef.current = { x: e.clientX, y: e.clientY, moved: false, ox: vp.ox, oy: vp.oy };\n };\n const onPointerMove = (e: React.PointerEvent) => {\n const d = dragRef.current;\n const rect = canvasRef.current!.getBoundingClientRect();\n if (d) {\n const dx = e.clientX - d.x;\n const dy = e.clientY - d.y;\n if (Math.abs(dx) + Math.abs(dy) > 3) d.moved = true;\n if (d.moved) setVp((v) => ({ ...v, ox: d.ox + dx, oy: d.oy + dy }));\n } else if (props.onHover) {\n const world = toWorld(e.clientX - rect.left, e.clientY - rect.top);\n props.onHover(hitTest(scene, world, 6 / vp.scale));\n }\n };\n const onPointerUp = (e: React.PointerEvent) => {\n const d = dragRef.current;\n dragRef.current = null;\n if (d && !d.moved && props.onSelect) {\n const rect = canvasRef.current!.getBoundingClientRect();\n const world = toWorld(e.clientX - rect.left, e.clientY - rect.top);\n props.onSelect(hitTest(scene, world, 6 / vp.scale));\n }\n };\n const onWheel = (e: React.WheelEvent) => {\n const rect = canvasRef.current!.getBoundingClientRect();\n const sx = e.clientX - rect.left;\n const sy = e.clientY - rect.top;\n const factor = Math.exp(-e.deltaY * 0.0015);\n setVp((v) => {\n const scale = Math.max(4, Math.min(400, v.scale * factor));\n const wx = (sx - v.ox) / v.scale;\n const wy = (sy - v.oy) / v.scale;\n return { scale, ox: sx - wx * scale, oy: sy - wy * scale };\n });\n };\n\n return (\n <div ref={containerRef} className={props.className} style={{ width: \"100%\", height: \"100%\", position: \"relative\", background: COLORS.bg }}>\n <canvas\n ref={canvasRef}\n style={{ width: \"100%\", height: \"100%\", display: \"block\", cursor: dragRef.current?.moved ? \"grabbing\" : \"default\", touchAction: \"none\" }}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerLeave={() => props.onHover?.(null)}\n onWheel={onWheel}\n onDoubleClick={fit}\n />\n </div>\n );\n}\n\nfunction draw(\n ctx: CanvasRenderingContext2D,\n size: { w: number; h: number },\n vp: Viewport,\n scene: PlanScene,\n ghost: PlanScene | null,\n selected: EntityRef | null,\n opts: { showGrid: boolean; showMeasurements: boolean; gridSize: number; toScreen: (p: Vec2) => Vec2 },\n) {\n const { toScreen } = opts;\n ctx.fillStyle = COLORS.bg;\n ctx.fillRect(0, 0, size.w, size.h);\n\n if (opts.showGrid) drawGrid(ctx, size, vp, opts.gridSize);\n\n if (ghost) {\n ctx.save();\n ctx.globalAlpha = 0.4;\n for (const w of ghost.walls) fillPolygon(ctx, w.polygon, COLORS.ghost, toScreen);\n ctx.restore();\n }\n\n // Room fills + labels.\n for (const r of scene.rooms) {\n fillPolygon(ctx, r.polygon, COLORS.room, toScreen);\n }\n // Walls.\n for (const w of scene.walls) {\n const isSel = selected?.kind === \"wall\" && selected.id === w.id;\n fillPolygon(ctx, w.polygon, isSel ? COLORS.select : COLORS.wall, toScreen, COLORS.wallStroke);\n }\n // Furniture footprints (under openings/labels, over rooms).\n for (const ob of scene.objects) {\n drawFurniture(ctx, ob, vp, toScreen, selected?.kind === \"object\" && selected.id === ob.id);\n }\n // Openings.\n for (const o of scene.openings) {\n drawOpening(ctx, o, toScreen, selected?.kind === \"opening\" && selected.id === o.id);\n }\n // Room labels last (on top).\n for (const r of scene.rooms) {\n const c = toScreen(r.centroid);\n const isSel = selected?.kind === \"room\" && selected.id === r.id;\n if (isSel) {\n ctx.save();\n ctx.beginPath();\n ctx.strokeStyle = COLORS.select;\n ctx.lineWidth = 2;\n polyPath(ctx, r.polygon, toScreen);\n ctx.stroke();\n ctx.restore();\n }\n ctx.fillStyle = COLORS.roomLabel;\n ctx.font = \"600 12px ui-sans-serif, system-ui, sans-serif\";\n ctx.textAlign = \"center\";\n ctx.fillText(r.name, c[0], c[1] - 2);\n if (opts.showMeasurements) {\n ctx.fillStyle = COLORS.dim;\n ctx.font = \"11px ui-monospace, monospace\";\n ctx.fillText(`${r.area.toFixed(1)} m²`, c[0], c[1] + 13);\n }\n }\n}\n\nfunction drawGrid(ctx: CanvasRenderingContext2D, size: { w: number; h: number }, vp: Viewport, gridSize: number) {\n const step = gridSize * vp.scale;\n if (step < 6) return;\n const startX = vp.ox % step;\n const startY = vp.oy % step;\n ctx.lineWidth = 1;\n for (let x = startX; x < size.w; x += step) {\n ctx.strokeStyle = COLORS.grid;\n ctx.beginPath();\n ctx.moveTo(x, 0);\n ctx.lineTo(x, size.h);\n ctx.stroke();\n }\n for (let y = startY; y < size.h; y += step) {\n ctx.strokeStyle = COLORS.grid;\n ctx.beginPath();\n ctx.moveTo(0, y);\n ctx.lineTo(size.w, y);\n ctx.stroke();\n }\n}\n\nfunction polyPath(ctx: CanvasRenderingContext2D, poly: Vec2[], toScreen: (p: Vec2) => Vec2) {\n poly.forEach((p, i) => {\n const s = toScreen(p);\n if (i === 0) ctx.moveTo(s[0], s[1]);\n else ctx.lineTo(s[0], s[1]);\n });\n ctx.closePath();\n}\n\nfunction fillPolygon(ctx: CanvasRenderingContext2D, poly: Vec2[], fill: string, toScreen: (p: Vec2) => Vec2, stroke?: string) {\n ctx.beginPath();\n polyPath(ctx, poly, toScreen);\n ctx.fillStyle = fill;\n ctx.fill();\n if (stroke) {\n ctx.lineWidth = 1;\n ctx.strokeStyle = stroke;\n ctx.stroke();\n }\n}\n\nfunction drawFurniture(\n ctx: CanvasRenderingContext2D,\n ob: PlanScene[\"objects\"][number],\n vp: Viewport,\n toScreen: (p: Vec2) => Vec2,\n selected: boolean,\n) {\n const c = toScreen(ob.center);\n const w = ob.size[0] * vp.scale;\n const h = ob.size[1] * vp.scale;\n ctx.save();\n ctx.translate(c[0], c[1]);\n if (ob.rotation) ctx.rotate(ob.rotation);\n const color = selected ? COLORS.select : furnitureColor(ob.type);\n ctx.fillStyle = color + \"33\"; // translucent fill\n ctx.strokeStyle = color;\n ctx.lineWidth = selected ? 2 : 1.25;\n const r = Math.min(4, w / 4, h / 4);\n roundRect(ctx, -w / 2, -h / 2, w, h, r);\n ctx.fill();\n ctx.stroke();\n if (Math.min(w, h) > 26) {\n ctx.fillStyle = COLORS.roomLabel;\n ctx.font = \"9px ui-sans-serif, system-ui, sans-serif\";\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(ob.type, 0, 0);\n }\n ctx.restore();\n}\n\nfunction roundRect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) {\n ctx.beginPath();\n ctx.moveTo(x + r, y);\n ctx.arcTo(x + w, y, x + w, y + h, r);\n ctx.arcTo(x + w, y + h, x, y + h, r);\n ctx.arcTo(x, y + h, x, y, r);\n ctx.arcTo(x, y, x + w, y, r);\n ctx.closePath();\n}\n\nfunction drawOpening(\n ctx: CanvasRenderingContext2D,\n o: PlanScene[\"openings\"][number],\n toScreen: (p: Vec2) => Vec2,\n selected: boolean,\n) {\n const a = toScreen(o.p0);\n const b = toScreen(o.p1);\n const color = selected ? COLORS.select : o.type === \"door\" ? COLORS.door : o.type === \"window\" ? COLORS.window : COLORS.opening;\n ctx.strokeStyle = color;\n ctx.lineWidth = selected ? 3 : 2;\n // Clear the wall under the opening for legibility.\n ctx.save();\n ctx.strokeStyle = COLORS.bg;\n ctx.lineWidth = 6;\n ctx.beginPath();\n ctx.moveTo(a[0], a[1]);\n ctx.lineTo(b[0], b[1]);\n ctx.stroke();\n ctx.restore();\n\n if (o.type === \"door\") {\n // Swing arc.\n const cx = a[0];\n const cy = a[1];\n const r = Math.hypot(b[0] - a[0], b[1] - a[1]);\n const start = Math.atan2(b[1] - a[1], b[0] - a[0]);\n ctx.strokeStyle = color;\n ctx.lineWidth = 1.5;\n ctx.beginPath();\n ctx.arc(cx, cy, r, start, start + Math.PI / 2);\n ctx.stroke();\n ctx.beginPath();\n ctx.moveTo(a[0], a[1]);\n ctx.lineTo(b[0], b[1]);\n ctx.stroke();\n } else {\n ctx.strokeStyle = color;\n ctx.lineWidth = selected ? 3 : 2;\n ctx.beginPath();\n ctx.moveTo(a[0], a[1]);\n ctx.lineTo(b[0], b[1]);\n ctx.stroke();\n }\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@react-arch/renderer-2d",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@react-arch/core": "0.1.0",
16
+ "@react-arch/geometry": "0.1.0",
17
+ "@react-arch/shared": "0.1.0"
18
+ },
19
+ "peerDependencies": {
20
+ "react": "^18.3.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^18.3.12",
24
+ "react": "^18.3.1",
25
+ "typescript": "^5.7.2"
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/react-arch/react-arch.git",
36
+ "directory": "packages/renderer-2d"
37
+ },
38
+ "license": "MIT",
39
+ "scripts": {
40
+ "typecheck": "tsc --noEmit",
41
+ "test": "vitest run --passWithNoTests",
42
+ "lint": "oxlint src",
43
+ "build": "tsdown"
44
+ }
45
+ }