From 4956de4910e508277f14c5e7544998f17ac5872e Mon Sep 17 00:00:00 2001 From: sudhir Date: Sun, 10 May 2026 12:51:57 +0530 Subject: [PATCH] Refine column support selection and polygon handling --- bun.lock | 7 -- .../src/components/ui/panels/column-panel.tsx | 79 +++++++++++-------- packages/viewer/package.json | 1 - .../renderers/site/site-renderer.tsx | 27 +++---- .../src/components/viewer/ground-occluder.tsx | 34 ++------ 5 files changed, 65 insertions(+), 83 deletions(-) diff --git a/bun.lock b/bun.lock index c0e5fbbe..6bbf8bb8 100644 --- a/bun.lock +++ b/bun.lock @@ -193,7 +193,6 @@ "name": "@pascal-app/viewer", "version": "0.6.0", "dependencies": { - "polygon-clipping": "^0.15.7", "three-bvh-csg": "^0.0.18", "three-mesh-bvh": "^0.9.8", "zustand": "^5", @@ -1289,8 +1288,6 @@ "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], - "polygon-clipping": ["polygon-clipping@0.15.7", "", { "dependencies": { "robust-predicates": "^3.0.2", "splaytree": "^3.1.0" } }, "sha512-nhfdr83ECBg6xtqOAJab1tbksbBAOMUltN60bU+llHVOL0e5Onm1WpAXXWXVB39L8AJFssoIhEVuy/S90MmotA=="], - "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], @@ -1351,8 +1348,6 @@ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - "robust-predicates": ["robust-predicates@3.0.3", "", {}, "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA=="], - "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -1409,8 +1404,6 @@ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "splaytree": ["splaytree@3.2.3", "", {}, "sha512-7OXrNWzy6CK+r7Ch9OLPBDTKfB6XlWHjX4P0RU5B3IgFuWPeYN0XtRtlexGRjgbQxpfaUve6jTAwBGWuGntz/w=="], - "stats-gl": ["stats-gl@2.4.2", "", { "dependencies": { "@types/three": "*", "three": "^0.170.0" } }, "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ=="], "stats.js": ["stats.js@0.17.0", "", {}, "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw=="], diff --git a/packages/editor/src/components/ui/panels/column-panel.tsx b/packages/editor/src/components/ui/panels/column-panel.tsx index 283dc2da..5e632a1c 100644 --- a/packages/editor/src/components/ui/panels/column-panel.tsx +++ b/packages/editor/src/components/ui/panels/column-panel.tsx @@ -11,6 +11,7 @@ import { useViewer } from '@pascal-app/viewer' import { Move, Trash2 } from 'lucide-react' import { useCallback } from 'react' import { sfxEmitter } from '../../../lib/sfx-bus' +import { cn } from '../../../lib/utils' import useEditor from '../../../store/use-editor' import { ActionButton, ActionGroup } from '../controls/action-button' import { PanelSection } from '../controls/panel-section' @@ -76,6 +77,20 @@ const COLUMN_PROPORTION_OPTIONS = Object.entries(COLUMN_PROPORTION_PRESETS).map( label: preset.label, })) +const SUPPORT_STYLE_OPTIONS: Array<{ label: string; value: ColumnNode['supportStyle'] }> = [ + { label: 'Vertical', value: 'vertical' }, + { label: 'A-Frame', value: 'a-frame' }, + { label: 'Y Support', value: 'y-frame' }, + { label: 'V Support', value: 'v-frame' }, + { label: 'X Brace', value: 'x-brace' }, + { label: 'K Brace', value: 'k-brace' }, + { label: 'Single Strut', value: 'single-strut' }, + { label: 'Tripod', value: 'tripod' }, + { label: 'Trestle', value: 'trestle' }, + { label: 'Portal Frame', value: 'portal-frame' }, + { label: 'Box Frame', value: 'box-frame' }, +] + function clamp(value: number, min: number, max: number) { return Math.min(max, Math.max(min, value)) } @@ -235,37 +250,39 @@ export function ColumnPanel() { - +
+ {SUPPORT_STYLE_OPTIONS.map((option) => { + const isSelected = supportStyle === option.value + return ( + + ) + })} +
{isBraceSupport ? ( <> { for (let i = 1; i < pts.length; i++) shape.lineTo(pts[i]![0], -pts[i]![1]) shape.closePath() - if (slabPolygons.length > 0) { - const multiPolygons = slabPolygons.map((p) => [ - p.map((pt) => [pt[0], -pt[1]] as [number, number]), - ]) - const unioned = polygonClipping.union( - multiPolygons[0] as polygonClipping.Polygon, - ...(multiPolygons.slice(1) as polygonClipping.Polygon[]), - ) - for (const geom of unioned) { - const ring = geom[0] - if (ring && ring.length > 0) { - const hole = new Path() - hole.moveTo(ring[0]![0], ring[0]![1]) - for (let i = 1; i < ring.length; i++) hole.lineTo(ring[i]![0], ring[i]![1]) - hole.closePath() - shape.holes.push(hole) - } + for (const polygon of slabPolygons) { + if (polygon.length < 3) continue + + const hole = new Path() + hole.moveTo(polygon[0]![0], -polygon[0]![1]) + for (let i = 1; i < polygon.length; i++) { + hole.lineTo(polygon[i]![0], -polygon[i]![1]) } + hole.closePath() + shape.holes.push(hole) } return shape diff --git a/packages/viewer/src/components/viewer/ground-occluder.tsx b/packages/viewer/src/components/viewer/ground-occluder.tsx index 54be8b51..c90cafa6 100644 --- a/packages/viewer/src/components/viewer/ground-occluder.tsx +++ b/packages/viewer/src/components/viewer/ground-occluder.tsx @@ -1,5 +1,4 @@ import { type LevelNode, useScene } from '@pascal-app/core' -import polygonClipping from 'polygon-clipping' import { useMemo } from 'react' import * as THREE from 'three' import useViewer from '../../store/use-viewer' @@ -63,33 +62,16 @@ export const GroundOccluder = () => { polygons.push(node.polygon as [number, number][]) }) - if (polygons.length > 0) { - // Format for polygon-clipping: [[[x, y], [x, y], ...]] - const multiPolygons = polygons.map((pts) => { - const ring = pts.map((p) => [p[0], -p[1]] as [number, number]) // Negate Y (which was Z) - return [ring] - }) + for (const polygon of polygons) { + if (polygon.length < 3) continue - // Union all polygons together to prevent artifacts from overlapping - const unionedPolygons = polygonClipping.union(multiPolygons[0]!, ...multiPolygons.slice(1)) - - // Add each resulting unioned polygon as a hole - for (const geom of unionedPolygons) { - // First ring in each geometry is the exterior ring - if (geom.length > 0) { - const ring = geom[0]! - const hole = new THREE.Path() - - if (ring.length > 0) { - hole.moveTo(ring[0]![0], ring[0]![1]) - for (let i = 1; i < ring.length; i++) { - hole.lineTo(ring[i]![0], ring[i]![1]) - } - hole.closePath() - s.holes.push(hole) - } - } + const hole = new THREE.Path() + hole.moveTo(polygon[0]![0], -polygon[0]![1]) + for (let i = 1; i < polygon.length; i++) { + hole.lineTo(polygon[i]![0], -polygon[i]![1]) } + hole.closePath() + s.holes.push(hole) } return s