@pascal-app/core 0.7.0 → 0.8.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 (76) hide show
  1. package/dist/events/bus.d.ts +9 -6
  2. package/dist/events/bus.d.ts.map +1 -1
  3. package/dist/events/bus.js +1 -1
  4. package/dist/lib/polygon-geometry.d.ts.map +1 -1
  5. package/dist/lib/space-detection.d.ts.map +1 -1
  6. package/dist/lib/space-detection.js +10 -8
  7. package/dist/material-library.d.ts.map +1 -1
  8. package/dist/material-library.js +20 -1
  9. package/dist/schema/asset-url.test.js +0 -4
  10. package/dist/schema/material.d.ts +2 -2
  11. package/dist/schema/nodes/ceiling.d.ts +1 -1
  12. package/dist/schema/nodes/column.d.ts +1 -1
  13. package/dist/schema/nodes/door.d.ts +1 -1
  14. package/dist/schema/nodes/fence.d.ts +2 -2
  15. package/dist/schema/nodes/fence.js +2 -2
  16. package/dist/schema/nodes/item.d.ts +12 -0
  17. package/dist/schema/nodes/item.d.ts.map +1 -1
  18. package/dist/schema/nodes/item.js +12 -0
  19. package/dist/schema/nodes/roof-segment.d.ts +3 -3
  20. package/dist/schema/nodes/roof.d.ts +6 -6
  21. package/dist/schema/nodes/roof.d.ts.map +1 -1
  22. package/dist/schema/nodes/roof.js +5 -5
  23. package/dist/schema/nodes/site.d.ts +6 -0
  24. package/dist/schema/nodes/site.d.ts.map +1 -1
  25. package/dist/schema/nodes/slab.d.ts +1 -1
  26. package/dist/schema/nodes/stair-segment.d.ts +1 -1
  27. package/dist/schema/nodes/stair.d.ts +6 -6
  28. package/dist/schema/nodes/stair.d.ts.map +1 -1
  29. package/dist/schema/nodes/stair.js +9 -7
  30. package/dist/schema/nodes/wall.d.ts +3 -3
  31. package/dist/schema/nodes/window.d.ts +1 -1
  32. package/dist/schema/types.d.ts +33 -21
  33. package/dist/schema/types.d.ts.map +1 -1
  34. package/dist/store/actions/node-actions.d.ts.map +1 -1
  35. package/dist/store/actions/node-actions.js +7 -5
  36. package/dist/store/use-scene.d.ts.map +1 -1
  37. package/dist/store/use-scene.js +11 -5
  38. package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -1
  39. package/dist/systems/stair/stair-opening-sync.js +17 -44
  40. package/dist/systems/stair/stair-opening-sync.test.js +0 -2
  41. package/dist/systems/wall/wall-curve.d.ts +1 -1
  42. package/dist/systems/wall/wall-curve.d.ts.map +1 -1
  43. package/dist/systems/wall/wall-curve.js +1 -1
  44. package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
  45. package/dist/systems/wall/wall-mitering.js +2 -6
  46. package/package.json +4 -3
  47. package/dist/materials.d.ts +0 -10
  48. package/dist/materials.d.ts.map +0 -1
  49. package/dist/materials.js +0 -22
  50. package/dist/systems/ceiling/ceiling-system.d.ts +0 -8
  51. package/dist/systems/ceiling/ceiling-system.d.ts.map +0 -1
  52. package/dist/systems/ceiling/ceiling-system.js +0 -92
  53. package/dist/systems/door/door-system.d.ts +0 -2
  54. package/dist/systems/door/door-system.d.ts.map +0 -1
  55. package/dist/systems/door/door-system.js +0 -195
  56. package/dist/systems/fence/fence-system.d.ts +0 -2
  57. package/dist/systems/fence/fence-system.d.ts.map +0 -1
  58. package/dist/systems/fence/fence-system.js +0 -187
  59. package/dist/systems/item/item-system.d.ts +0 -2
  60. package/dist/systems/item/item-system.d.ts.map +0 -1
  61. package/dist/systems/item/item-system.js +0 -48
  62. package/dist/systems/roof/roof-system.d.ts +0 -16
  63. package/dist/systems/roof/roof-system.d.ts.map +0 -1
  64. package/dist/systems/roof/roof-system.js +0 -797
  65. package/dist/systems/slab/slab-system.d.ts +0 -8
  66. package/dist/systems/slab/slab-system.d.ts.map +0 -1
  67. package/dist/systems/slab/slab-system.js +0 -214
  68. package/dist/systems/stair/stair-system.d.ts +0 -2
  69. package/dist/systems/stair/stair-system.d.ts.map +0 -1
  70. package/dist/systems/stair/stair-system.js +0 -776
  71. package/dist/systems/wall/wall-system.d.ts +0 -12
  72. package/dist/systems/wall/wall-system.d.ts.map +0 -1
  73. package/dist/systems/wall/wall-system.js +0 -455
  74. package/dist/systems/window/window-system.d.ts +0 -2
  75. package/dist/systems/window/window-system.d.ts.map +0 -1
  76. package/dist/systems/window/window-system.js +0 -131
@@ -1,9 +1,8 @@
1
1
  import { resolveLevelId } from '../../hooks/spatial-grid/spatial-grid-sync';
2
2
  import { DEFAULT_WALL_HEIGHT } from '../wall/wall-footprint';
3
- const CURVED_STAIR_SLAB_OPENING_RATIO = 0.9;
3
+ const CURVED_STAIR_SLAB_OPENING_RATIO = 0.8;
4
4
  const STRAIGHT_STAIR_TARGET_THRESHOLD_MIN = 0.35;
5
5
  const STAIR_SLAB_OPENING_TIGHTENING = 0;
6
- const CURVED_STAIR_OPENING_STEP_PADDING = 3;
7
6
  function clamp(value, min, max) {
8
7
  return Math.min(max, Math.max(min, value));
9
8
  }
@@ -187,7 +186,7 @@ function polygonArea(points) {
187
186
  for (let index = 0; index < points.length; index += 1) {
188
187
  const current = points[index];
189
188
  const next = points[(index + 1) % points.length];
190
- if (!current || !next)
189
+ if (!(current && next))
191
190
  continue;
192
191
  area += current[0] * next[1] - next[0] * current[1];
193
192
  }
@@ -319,54 +318,30 @@ function buildUnionPolygonsFromRects(rects) {
319
318
  }
320
319
  return polygons;
321
320
  }
322
- function getCurvedOpeningStepCount(stair, innerRadius, outerRadius, totalSweep) {
323
- const stepCount = Math.max(2, Math.round(stair.stepCount ?? 10));
324
- const stepSweep = Math.abs(totalSweep) / stepCount;
325
- const midRadius = Math.max((innerRadius + outerRadius) * 0.5, 0.01);
326
- const treadDepth = Math.max(stepSweep * midRadius, 0.2);
327
- return Math.min(stepCount, Math.max(1, Math.ceil(1.8 / treadDepth), Math.ceil(stepCount * CURVED_STAIR_SLAB_OPENING_RATIO)));
328
- }
329
- function buildArcOpeningPolygon(stair, innerRadius, outerRadius, startAngle, endAngle) {
330
- const sweep = endAngle - startAngle;
331
- const segmentCount = Math.max(10, Math.min(32, Math.ceil(Math.abs(sweep) / (Math.PI / 24) + Math.max(stair.stepCount ?? 1, 1) * 0.5)));
321
+ function getCurvedOpeningPolygon(stair) {
322
+ const width = Math.max(stair.width ?? 1, 0.4);
323
+ const innerRadius = Math.max(0.2, stair.innerRadius ?? 0.9);
324
+ const outerRadius = innerRadius + width;
325
+ const totalSweep = stair.sweepAngle ?? Math.PI / 2;
326
+ const openingSweep = Math.sign(totalSweep || 1) *
327
+ Math.max(Math.abs(totalSweep) * CURVED_STAIR_SLAB_OPENING_RATIO, Math.abs(totalSweep) / Math.max(stair.stepCount ?? 1, 1));
328
+ const startAngle = totalSweep / 2 - openingSweep;
329
+ const endAngle = totalSweep / 2;
330
+ const segmentCount = Math.max(10, Math.min(32, Math.ceil(Math.abs(openingSweep) / (Math.PI / 24) + Math.max(stair.stepCount ?? 1, 1) * 0.5)));
332
331
  const outerPoints = [];
333
332
  const innerPoints = [];
334
333
  for (let index = 0; index <= segmentCount; index++) {
335
334
  const t = index / segmentCount;
336
- const angle = startAngle + sweep * t;
335
+ const angle = startAngle + (endAngle - startAngle) * t;
337
336
  outerPoints.push(toWorldPlanPoint(stair, Math.cos(angle) * outerRadius, Math.sin(angle) * outerRadius));
338
337
  }
339
338
  for (let index = segmentCount; index >= 0; index--) {
340
339
  const t = index / segmentCount;
341
- const angle = startAngle + sweep * t;
340
+ const angle = startAngle + (endAngle - startAngle) * t;
342
341
  innerPoints.push(toWorldPlanPoint(stair, Math.cos(angle) * innerRadius, Math.sin(angle) * innerRadius));
343
342
  }
344
343
  return [...outerPoints, ...innerPoints];
345
344
  }
346
- function getCurvedOpeningPolygon(stair, targetElevation) {
347
- const width = Math.max(stair.width ?? 1, 0.4);
348
- const innerRadius = Math.max(0.2, stair.innerRadius ?? 0.9);
349
- const outerRadius = innerRadius + width;
350
- const totalSweep = stair.sweepAngle ?? Math.PI / 2;
351
- const stepCount = Math.max(2, Math.round(stair.stepCount ?? 10));
352
- const stepHeight = Math.max(stair.totalRise ?? 2.5, 0.1) / stepCount;
353
- const stepSweep = totalSweep / stepCount;
354
- const targetThreshold = Math.max(stepHeight * 2, STRAIGHT_STAIR_TARGET_THRESHOLD_MIN);
355
- const endAngle = totalSweep / 2;
356
- const fallbackStartStepIndex = Math.max(0, stepCount - getCurvedOpeningStepCount(stair, innerRadius, outerRadius, totalSweep));
357
- let startStepIndex = fallbackStartStepIndex;
358
- if (typeof targetElevation === 'number') {
359
- for (let index = 0; index < stepCount; index += 1) {
360
- const stepTopElevation = stepHeight * (index + 1);
361
- if (stepTopElevation >= targetElevation - targetThreshold) {
362
- startStepIndex = Math.max(0, Math.min(fallbackStartStepIndex, index - CURVED_STAIR_OPENING_STEP_PADDING));
363
- break;
364
- }
365
- }
366
- }
367
- const startAngle = -totalSweep / 2 + stepSweep * startStepIndex;
368
- return buildArcOpeningPolygon(stair, innerRadius, outerRadius, startAngle, endAngle);
369
- }
370
345
  function getSpiralOpeningPolygon(stair) {
371
346
  const radius = Math.max(0.05, stair.innerRadius ?? 0.9) + Math.max(stair.width ?? 1, 0.4);
372
347
  const segmentCount = 48;
@@ -439,7 +414,7 @@ function getStairOpeningPolygons(stair, nodes, targetElevation) {
439
414
  return [];
440
415
  }
441
416
  if (stair.stairType === 'curved') {
442
- return [getCurvedOpeningPolygon(stair, targetElevation)];
417
+ return [getCurvedOpeningPolygon(stair)];
443
418
  }
444
419
  if (stair.stairType === 'spiral') {
445
420
  return [getSpiralOpeningPolygon(stair)];
@@ -528,8 +503,7 @@ export function syncAutoStairOpenings(nodes) {
528
503
  .filter((hole) => polygonContainsPolygon(slab.polygon, hole.polygon));
529
504
  const nextHoles = [...manualHoles, ...stairHoles.map((hole) => hole.polygon)];
530
505
  const nextMetadata = [...manualMetadata, ...stairHoles.map((hole) => hole.metadata)];
531
- if (!polygonsEqual(existingHoles, nextHoles) ||
532
- !metadataEqual(existingMetadata, nextMetadata)) {
506
+ if (!(polygonsEqual(existingHoles, nextHoles) && metadataEqual(existingMetadata, nextMetadata))) {
533
507
  updates.push({
534
508
  id: slab.id,
535
509
  data: {
@@ -561,8 +535,7 @@ export function syncAutoStairOpenings(nodes) {
561
535
  .filter((hole) => polygonContainsPolygon(ceiling.polygon, hole.polygon));
562
536
  const nextHoles = [...manualHoles, ...stairHoles.map((hole) => hole.polygon)];
563
537
  const nextMetadata = [...manualMetadata, ...stairHoles.map((hole) => hole.metadata)];
564
- if (!polygonsEqual(existingHoles, nextHoles) ||
565
- !metadataEqual(existingMetadata, nextMetadata)) {
538
+ if (!(polygonsEqual(existingHoles, nextHoles) && metadataEqual(existingMetadata, nextMetadata))) {
566
539
  updates.push({
567
540
  id: ceiling.id,
568
541
  data: {
@@ -1,5 +1,3 @@
1
- // @ts-expect-error — bun:test is provided by the Bun runtime; core does not
2
- // depend on @types/bun so the import type is unresolved at compile time.
3
1
  import { describe, expect, test } from 'bun:test';
4
2
  import { BuildingNode, LevelNode, SlabNode, StairNode, StairSegmentNode } from '../../schema';
5
3
  import { syncAutoStairOpenings } from './stair-opening-sync';
@@ -1,5 +1,5 @@
1
- import type { Point2D } from './wall-mitering';
2
1
  import type { FenceNode, WallNode } from '../../schema';
2
+ import type { Point2D } from './wall-mitering';
3
3
  type WallCurveLike = Pick<WallNode | FenceNode, 'start' | 'end' | 'curveOffset'>;
4
4
  type CurveFrame = {
5
5
  point: Point2D;
@@ -1 +1 @@
1
- {"version":3,"file":"wall-curve.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-curve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAKvD,KAAK,aAAa,GAAG,IAAI,CAAC,QAAQ,GAAG,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAC,CAAA;AAEhF,KAAK,UAAU,GAAG;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,KAAK,yBAAyB,GAAG;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAcD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE9D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,UAErD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,UAExD;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,aAAa,UAE5D;AAWD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,UAG3E;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,aAAa,UAI5D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,WAE/C;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa;;;;;;;;;;;;;EA6BpD;AA+BD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,CAwC9E;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,WAE7D;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,SAA0B,aAG3F;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,SAA0B,UASzF;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,GAAG,WAAW,CAAC,EAC/E,QAAQ,SAA0B,EAClC,cAAc,CAAC,EAAE,yBAAyB,aA2B3C"}
1
+ {"version":3,"file":"wall-curve.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-curve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AACvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAK9C,KAAK,aAAa,GAAG,IAAI,CAAC,QAAQ,GAAG,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAC,CAAA;AAEhF,KAAK,UAAU,GAAG;IAChB,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,KAAK,yBAAyB,GAAG;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAcD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE9D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE5D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,UAErD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,aAAa,UAExD;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,aAAa,UAE5D;AAWD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,UAG3E;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,aAAa,UAI5D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,WAE/C;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa;;;;;;;;;;;;;EA6BpD;AA+BD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,CAwC9E;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,WAE7D;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,SAA0B,aAM3F;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,SAA0B,UASzF;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,IAAI,CAAC,QAAQ,GAAG,SAAS,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,GAAG,WAAW,CAAC,EAC/E,QAAQ,SAA0B,EAClC,cAAc,CAAC,EAAE,yBAAyB,aA2B3C"}
@@ -78,7 +78,7 @@ function getWallArcData(wall) {
78
78
  return null;
79
79
  }
80
80
  const absSagitta = Math.abs(sagitta);
81
- const radius = chord.length * chord.length / (8 * absSagitta) + absSagitta / 2;
81
+ const radius = (chord.length * chord.length) / (8 * absSagitta) + absSagitta / 2;
82
82
  const centerOffset = radius - absSagitta;
83
83
  const direction = Math.sign(sagitta) || 1;
84
84
  const center = {
@@ -1 +1 @@
1
- {"version":3,"file":"wall-mitering.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-mitering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAO5C,MAAM,WAAW,OAAO;IACtB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;CAClB;AASD,KAAK,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAGzE,KAAK,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;AAQlD,iBAAS,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,SAAY,GAAG,MAAM,CAG7D;AA8CD,UAAU,QAAQ;IAChB,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAA;KAAE,CAAC,CAAA;CACpF;AA+ND,MAAM,WAAW,aAAa;IAE5B,YAAY,EAAE,YAAY,CAAA;IAE1B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CACjC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,aAAa,CAWrE;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,aAAa,GACvB,uBAAuB,GAAG,IAAI,CA0BhC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CA8C/F;AAGD,OAAO,EAAE,UAAU,EAAE,CAAA"}
1
+ {"version":3,"file":"wall-mitering.d.ts","sourceRoot":"","sources":["../../../src/systems/wall/wall-mitering.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAO5C,MAAM,WAAW,OAAO;IACtB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,OAAO,CAAA;IAClB,UAAU,EAAE,OAAO,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;CAClB;AASD,KAAK,iBAAiB,GAAG,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,CAAA;AAGzE,KAAK,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;AAQlD,iBAAS,UAAU,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,SAAY,GAAG,MAAM,CAG7D;AA8CD,UAAU,QAAQ;IAChB,YAAY,EAAE,OAAO,CAAA;IACrB,cAAc,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,aAAa,CAAA;KAAE,CAAC,CAAA;CACpF;AAoND,MAAM,WAAW,aAAa;IAE5B,YAAY,EAAE,YAAY,CAAA;IAE1B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;CACjC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,aAAa,CAWrE;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,aAAa,GACvB,uBAAuB,GAAG,IAAI,CA0BhC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CA8C/F;AAGD,OAAO,EAAE,UAAU,EAAE,CAAA"}
@@ -89,9 +89,7 @@ function getWallDirectionFromJunction(wall, endType) {
89
89
  }
90
90
  if (isCurvedWall(wall)) {
91
91
  const frame = getWallCurveFrameAt(wall, endType === 'start' ? 0 : 1);
92
- return endType === 'start'
93
- ? frame.tangent
94
- : { x: -frame.tangent.x, y: -frame.tangent.y };
92
+ return endType === 'start' ? frame.tangent : { x: -frame.tangent.x, y: -frame.tangent.y };
95
93
  }
96
94
  return endType === 'start'
97
95
  ? { x: wall.end[0] - wall.start[0], y: wall.end[1] - wall.start[1] }
@@ -102,9 +100,7 @@ function getWallBoundaryFrame(wall, endType) {
102
100
  const frame = getWallCurveFrameAt(wall, endType === 'start' ? 0 : 1);
103
101
  return {
104
102
  point: frame.point,
105
- tangent: endType === 'start'
106
- ? frame.tangent
107
- : { x: -frame.tangent.x, y: -frame.tangent.y },
103
+ tangent: endType === 'start' ? frame.tangent : { x: -frame.tangent.x, y: -frame.tangent.y },
108
104
  normal: frame.normal,
109
105
  };
110
106
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pascal-app/core",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Core library for Pascal 3D building editor",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -73,9 +73,10 @@
73
73
  },
74
74
  "devDependencies": {
75
75
  "@pascal/typescript-config": "*",
76
+ "@types/bun": "^1.3.0",
76
77
  "@types/react": "^19.2.2",
77
- "typescript": "5.9.3",
78
- "@types/three": "^0.184.0"
78
+ "@types/three": "^0.184.0",
79
+ "typescript": "6.0.2"
79
80
  },
80
81
  "keywords": [
81
82
  "3d",
@@ -1,10 +0,0 @@
1
- import { MeshStandardNodeMaterial } from 'three/webgpu';
2
- /**
3
- * Shared base material for structural elements: walls, frames, slabs, roof.
4
- */
5
- export declare const baseMaterial: MeshStandardNodeMaterial;
6
- /**
7
- * Shared glass material for windows, glazed door panels, and glass items.
8
- */
9
- export declare const glassMaterial: MeshStandardNodeMaterial;
10
- //# sourceMappingURL=materials.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"materials.d.ts","sourceRoot":"","sources":["../src/materials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,wBAAwB,EAAE,MAAM,cAAc,CAAA;AAEnE;;GAEG;AACH,eAAO,MAAM,YAAY,0BAIvB,CAAA;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,0BASxB,CAAA"}
package/dist/materials.js DELETED
@@ -1,22 +0,0 @@
1
- import { DoubleSide, MeshStandardNodeMaterial } from 'three/webgpu';
2
- /**
3
- * Shared base material for structural elements: walls, frames, slabs, roof.
4
- */
5
- export const baseMaterial = new MeshStandardNodeMaterial({
6
- color: '#f2f0ed',
7
- roughness: 0.5,
8
- metalness: 0,
9
- });
10
- /**
11
- * Shared glass material for windows, glazed door panels, and glass items.
12
- */
13
- export const glassMaterial = new MeshStandardNodeMaterial({
14
- name: 'glass',
15
- color: 'lightblue',
16
- roughness: 0.05,
17
- metalness: 0.1,
18
- transparent: true,
19
- opacity: 0.35,
20
- side: DoubleSide,
21
- depthWrite: false,
22
- });
@@ -1,8 +0,0 @@
1
- import * as THREE from 'three';
2
- import type { CeilingNode } from '../../schema';
3
- export declare const CeilingSystem: () => null;
4
- /**
5
- * Generates flat ceiling geometry from polygon (no extrusion)
6
- */
7
- export declare function generateCeilingGeometry(ceilingNode: CeilingNode): THREE.BufferGeometry;
8
- //# sourceMappingURL=ceiling-system.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,cAAc,CAAA;AAc1D,eAAO,MAAM,aAAa,YAuBzB,CAAA;AAqBD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,KAAK,CAAC,cAAc,CAgDtF"}
@@ -1,92 +0,0 @@
1
- import { useFrame } from '@react-three/fiber';
2
- import * as THREE from 'three';
3
- import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
4
- import useScene from '../../store/use-scene';
5
- function ensureUv2Attribute(geometry) {
6
- const uv = geometry.getAttribute('uv');
7
- if (!uv)
8
- return;
9
- geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(uv.array), 2));
10
- }
11
- // ============================================================================
12
- // CEILING SYSTEM
13
- // ============================================================================
14
- export const CeilingSystem = () => {
15
- const dirtyNodes = useScene((state) => state.dirtyNodes);
16
- const clearDirty = useScene((state) => state.clearDirty);
17
- useFrame(() => {
18
- if (dirtyNodes.size === 0)
19
- return;
20
- const nodes = useScene.getState().nodes;
21
- // Process dirty ceilings
22
- dirtyNodes.forEach((id) => {
23
- const node = nodes[id];
24
- if (!node || node.type !== 'ceiling')
25
- return;
26
- const mesh = sceneRegistry.nodes.get(id);
27
- if (mesh) {
28
- updateCeilingGeometry(node, mesh);
29
- clearDirty(id);
30
- }
31
- // If mesh not found, keep it dirty for next frame
32
- });
33
- });
34
- return null;
35
- };
36
- /**
37
- * Updates the geometry for a single ceiling
38
- */
39
- function updateCeilingGeometry(node, mesh) {
40
- const newGeo = generateCeilingGeometry(node);
41
- mesh.geometry.dispose();
42
- mesh.geometry = newGeo;
43
- const gridMesh = mesh.getObjectByName('ceiling-grid');
44
- if (gridMesh) {
45
- gridMesh.geometry.dispose();
46
- gridMesh.geometry = newGeo;
47
- }
48
- // Position at the ceiling height
49
- mesh.position.y = (node.height ?? 2.5) - 0.01; // Slight offset to avoid z-fighting with upper-level slabs
50
- }
51
- /**
52
- * Generates flat ceiling geometry from polygon (no extrusion)
53
- */
54
- export function generateCeilingGeometry(ceilingNode) {
55
- const polygon = ceilingNode.polygon;
56
- if (polygon.length < 3) {
57
- return new THREE.BufferGeometry();
58
- }
59
- // Create shape from polygon
60
- // Shape is in X-Y plane, we'll rotate to X-Z plane
61
- const shape = new THREE.Shape();
62
- const firstPt = polygon[0];
63
- // Negate Y (which becomes Z) to get correct orientation after rotation
64
- shape.moveTo(firstPt[0], -firstPt[1]);
65
- for (let i = 1; i < polygon.length; i++) {
66
- const pt = polygon[i];
67
- shape.lineTo(pt[0], -pt[1]);
68
- }
69
- shape.closePath();
70
- // Add holes to the shape
71
- const holes = ceilingNode.holes || [];
72
- for (const holePolygon of holes) {
73
- if (holePolygon.length < 3)
74
- continue;
75
- const holePath = new THREE.Path();
76
- const holeFirstPt = holePolygon[0];
77
- holePath.moveTo(holeFirstPt[0], -holeFirstPt[1]);
78
- for (let i = 1; i < holePolygon.length; i++) {
79
- const pt = holePolygon[i];
80
- holePath.lineTo(pt[0], -pt[1]);
81
- }
82
- holePath.closePath();
83
- shape.holes.push(holePath);
84
- }
85
- // Create flat shape geometry (no extrusion)
86
- const geometry = new THREE.ShapeGeometry(shape);
87
- // Rotate so the shape lies flat in X-Z plane
88
- geometry.rotateX(-Math.PI / 2);
89
- geometry.computeVertexNormals();
90
- ensureUv2Attribute(geometry);
91
- return geometry;
92
- }
@@ -1,2 +0,0 @@
1
- export declare const DoorSystem: () => null;
2
- //# sourceMappingURL=door-system.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"door-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-system.tsx"],"names":[],"mappings":"AAUA,eAAO,MAAM,UAAU,YA2BtB,CAAA"}
@@ -1,195 +0,0 @@
1
- import { useFrame } from '@react-three/fiber';
2
- import * as THREE from 'three';
3
- import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
4
- import { baseMaterial, glassMaterial } from '../../materials';
5
- import useScene from '../../store/use-scene';
6
- // Invisible material for root mesh — used as selection hitbox only
7
- const hitboxMaterial = new THREE.MeshBasicMaterial({ visible: false });
8
- export const DoorSystem = () => {
9
- const dirtyNodes = useScene((state) => state.dirtyNodes);
10
- const clearDirty = useScene((state) => state.clearDirty);
11
- useFrame(() => {
12
- if (dirtyNodes.size === 0)
13
- return;
14
- const nodes = useScene.getState().nodes;
15
- dirtyNodes.forEach((id) => {
16
- const node = nodes[id];
17
- if (!node || node.type !== 'door')
18
- return;
19
- const mesh = sceneRegistry.nodes.get(id);
20
- if (!mesh)
21
- return; // Keep dirty until mesh mounts
22
- updateDoorMesh(node, mesh);
23
- clearDirty(id);
24
- // Rebuild the parent wall so its cutout reflects the updated door geometry
25
- if (node.parentId) {
26
- useScene.getState().dirtyNodes.add(node.parentId);
27
- }
28
- });
29
- }, 3);
30
- return null;
31
- };
32
- function addBox(parent, material, w, h, d, x, y, z) {
33
- const m = new THREE.Mesh(new THREE.BoxGeometry(w, h, d), material);
34
- m.position.set(x, y, z);
35
- parent.add(m);
36
- }
37
- function updateDoorMesh(node, mesh) {
38
- // Root mesh is an invisible hitbox; all visuals live in child meshes
39
- mesh.geometry.dispose();
40
- mesh.geometry = new THREE.BoxGeometry(node.width, node.height, node.frameDepth);
41
- mesh.material = hitboxMaterial;
42
- // Sync transform from node (React may lag behind the system by a frame during drag)
43
- mesh.position.set(node.position[0], node.position[1], node.position[2]);
44
- mesh.rotation.set(node.rotation[0], node.rotation[1], node.rotation[2]);
45
- // Dispose and remove all old visual children; preserve 'cutout'
46
- for (const child of [...mesh.children]) {
47
- if (child.name === 'cutout')
48
- continue;
49
- if (child instanceof THREE.Mesh)
50
- child.geometry.dispose();
51
- mesh.remove(child);
52
- }
53
- const { width, height, frameThickness, frameDepth, threshold, thresholdHeight, segments, handle, handleHeight, handleSide, doorCloser, panicBar, panicBarHeight, contentPadding, hingesSide, } = node;
54
- // Leaf occupies the full opening (no bottom frame bar — door opens to floor)
55
- const leafW = width - 2 * frameThickness;
56
- const leafH = height - frameThickness; // only top frame
57
- const leafDepth = 0.04;
58
- // Leaf center is shifted down from door center by half the top frame
59
- const leafCenterY = -frameThickness / 2;
60
- // ── Frame members ──
61
- // Left post — full height
62
- addBox(mesh, baseMaterial, frameThickness, height, frameDepth, -width / 2 + frameThickness / 2, 0, 0);
63
- // Right post — full height
64
- addBox(mesh, baseMaterial, frameThickness, height, frameDepth, width / 2 - frameThickness / 2, 0, 0);
65
- // Head (top bar) — full width
66
- addBox(mesh, baseMaterial, width, frameThickness, frameDepth, 0, height / 2 - frameThickness / 2, 0);
67
- // ── Threshold (inside the frame) ──
68
- if (threshold) {
69
- addBox(mesh, baseMaterial, leafW, thresholdHeight, frameDepth, 0, -height / 2 + thresholdHeight / 2, 0);
70
- }
71
- // ── Leaf — contentPadding border strips (no full backing; glass areas are open) ──
72
- const cpX = contentPadding[0];
73
- const cpY = contentPadding[1];
74
- if (cpY > 0) {
75
- // Top strip
76
- addBox(mesh, baseMaterial, leafW, cpY, leafDepth, 0, leafCenterY + leafH / 2 - cpY / 2, 0);
77
- // Bottom strip
78
- addBox(mesh, baseMaterial, leafW, cpY, leafDepth, 0, leafCenterY - leafH / 2 + cpY / 2, 0);
79
- }
80
- if (cpX > 0) {
81
- const innerH = leafH - 2 * cpY;
82
- // Left strip
83
- addBox(mesh, baseMaterial, cpX, innerH, leafDepth, -leafW / 2 + cpX / 2, leafCenterY, 0);
84
- // Right strip
85
- addBox(mesh, baseMaterial, cpX, innerH, leafDepth, leafW / 2 - cpX / 2, leafCenterY, 0);
86
- }
87
- // Content area inside padding
88
- const contentW = leafW - 2 * cpX;
89
- const contentH = leafH - 2 * cpY;
90
- // ── Segments (stacked top to bottom within content area) ──
91
- const totalRatio = segments.reduce((sum, s) => sum + s.heightRatio, 0);
92
- const contentTop = leafCenterY + contentH / 2;
93
- let segY = contentTop;
94
- for (const seg of segments) {
95
- const segH = (seg.heightRatio / totalRatio) * contentH;
96
- const segCenterY = segY - segH / 2;
97
- const numCols = seg.columnRatios.length;
98
- const colSum = seg.columnRatios.reduce((a, b) => a + b, 0);
99
- const usableW = contentW - (numCols - 1) * seg.dividerThickness;
100
- const colWidths = seg.columnRatios.map((r) => (r / colSum) * usableW);
101
- // Column x-centers (relative to mesh center)
102
- const colXCenters = [];
103
- let cx = -contentW / 2;
104
- for (let c = 0; c < numCols; c++) {
105
- colXCenters.push(cx + colWidths[c] / 2);
106
- cx += colWidths[c];
107
- if (c < numCols - 1)
108
- cx += seg.dividerThickness;
109
- }
110
- // Column dividers within this segment
111
- cx = -contentW / 2;
112
- for (let c = 0; c < numCols - 1; c++) {
113
- cx += colWidths[c];
114
- addBox(mesh, baseMaterial, seg.dividerThickness, segH, leafDepth + 0.001, cx + seg.dividerThickness / 2, segCenterY, 0);
115
- cx += seg.dividerThickness;
116
- }
117
- // Segment content per column
118
- for (let c = 0; c < numCols; c++) {
119
- const colW = colWidths[c];
120
- const colX = colXCenters[c];
121
- if (seg.type === 'glass') {
122
- // Glass only — no opaque backing so it's truly transparent
123
- const glassDepth = Math.max(0.004, leafDepth * 0.15);
124
- addBox(mesh, glassMaterial, colW, segH, glassDepth, colX, segCenterY, 0);
125
- }
126
- else if (seg.type === 'panel') {
127
- // Opaque leaf backing for this column
128
- addBox(mesh, baseMaterial, colW, segH, leafDepth, colX, segCenterY, 0);
129
- // Raised panel detail
130
- const panelW = colW - 2 * seg.panelInset;
131
- const panelH = segH - 2 * seg.panelInset;
132
- if (panelW > 0.01 && panelH > 0.01) {
133
- const effectiveDepth = Math.abs(seg.panelDepth) < 0.002 ? 0.005 : Math.abs(seg.panelDepth);
134
- const panelZ = leafDepth / 2 + effectiveDepth / 2;
135
- addBox(mesh, baseMaterial, panelW, panelH, effectiveDepth, colX, segCenterY, panelZ);
136
- }
137
- }
138
- else {
139
- // 'empty' — opaque backing, no detail
140
- addBox(mesh, baseMaterial, colW, segH, leafDepth, colX, segCenterY, 0);
141
- }
142
- }
143
- segY -= segH;
144
- }
145
- // ── Handle ──
146
- if (handle) {
147
- // Convert from floor-based height to mesh-center-based Y
148
- const handleY = handleHeight - height / 2;
149
- // Handle grip sits on the front face (+Z) of the leaf
150
- const faceZ = leafDepth / 2;
151
- // X position: handleSide refers to which side the grip is on
152
- const handleX = handleSide === 'right' ? leafW / 2 - 0.045 : -leafW / 2 + 0.045;
153
- // Backplate
154
- addBox(mesh, baseMaterial, 0.028, 0.14, 0.01, handleX, handleY, faceZ + 0.005);
155
- // Grip lever
156
- addBox(mesh, baseMaterial, 0.022, 0.1, 0.035, handleX, handleY, faceZ + 0.025);
157
- }
158
- // ── Door closer (commercial hardware at top) ──
159
- if (doorCloser) {
160
- const closerY = leafCenterY + leafH / 2 - 0.04;
161
- // Body
162
- addBox(mesh, baseMaterial, 0.28, 0.055, 0.055, 0, closerY, leafDepth / 2 + 0.03);
163
- // Arm (simplified as thin bar to frame side)
164
- addBox(mesh, baseMaterial, 0.14, 0.015, 0.015, leafW / 4, closerY + 0.025, leafDepth / 2 + 0.015);
165
- }
166
- // ── Panic bar ──
167
- if (panicBar) {
168
- const barY = panicBarHeight - height / 2;
169
- addBox(mesh, baseMaterial, leafW * 0.72, 0.04, 0.055, 0, barY, leafDepth / 2 + 0.03);
170
- }
171
- // ── Hinges (3 knuckle-style hinges on the hinge side) ──
172
- {
173
- const hingeX = hingesSide === 'right' ? leafW / 2 - 0.012 : -leafW / 2 + 0.012;
174
- const hingeZ = 0; // centered in leaf depth
175
- const hingeH = 0.1;
176
- const hingeW = 0.024;
177
- const hingeD = leafDepth + 0.016;
178
- // Bottom hinge ~0.25m from floor, middle hinge, top hinge ~0.25m from top
179
- const leafBottom = leafCenterY - leafH / 2;
180
- const leafTop = leafCenterY + leafH / 2;
181
- addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeX, leafBottom + 0.25, hingeZ);
182
- addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeX, (leafBottom + leafTop) / 2, hingeZ);
183
- addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeX, leafTop - 0.25, hingeZ);
184
- }
185
- // ── Cutout (for wall CSG) — always full door dimensions, 1m deep ──
186
- let cutout = mesh.getObjectByName('cutout');
187
- if (!cutout) {
188
- cutout = new THREE.Mesh();
189
- cutout.name = 'cutout';
190
- mesh.add(cutout);
191
- }
192
- cutout.geometry.dispose();
193
- cutout.geometry = new THREE.BoxGeometry(node.width, node.height, 1.0);
194
- cutout.visible = false;
195
- }
@@ -1,2 +0,0 @@
1
- export declare const FenceSystem: () => null;
2
- //# sourceMappingURL=fence-system.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fence-system.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-system.tsx"],"names":[],"mappings":"AAyQA,eAAO,MAAM,WAAW,YAiBvB,CAAA"}