fix hinge + materials

This commit is contained in:
wass08
2026-02-25 14:50:17 +09:00
parent 7608cad748
commit b114e60df7
3 changed files with 42 additions and 59 deletions
@@ -116,7 +116,7 @@ export function DoorPanel() {
if (!node || node.type !== 'door' || selectedIds.length !== 1) return null if (!node || node.type !== 'door' || selectedIds.length !== 1) return null
return ( return (
<div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-82 flex-col overflow-hidden rounded-lg border border-border bg-background/95 shadow-xl backdrop-blur-md"> <div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-82 flex-col overflow-hidden rounded-lg border border-border bg-background/95 shadow-xl backdrop-blur-md max-h-[calc(100dvh-100px)]">
{/* Header */} {/* Header */}
<div className="flex items-center justify-between gap-2 border-b p-3"> <div className="flex items-center justify-between gap-2 border-b p-3">
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
+38 -56
View File
@@ -5,23 +5,9 @@ import { sceneRegistry } from '../../hooks/scene-registry/scene-registry'
import type { AnyNodeId, DoorNode } from '../../schema' import type { AnyNodeId, DoorNode } from '../../schema'
import useScene from '../../store/use-scene' import useScene from '../../store/use-scene'
const frameMaterial = new MeshStandardNodeMaterial({ const baseMaterial = new MeshStandardNodeMaterial({
name: 'door-frame', name: 'door-base',
color: '#e8e8e8', color: '#f2f0ed',
roughness: 0.6,
metalness: 0,
})
const leafMaterial = new MeshStandardNodeMaterial({
name: 'door-leaf',
color: '#d0c8b8',
roughness: 0.5,
metalness: 0,
})
const panelMaterial = new MeshStandardNodeMaterial({
name: 'door-panel',
color: '#c5bdb0',
roughness: 0.5, roughness: 0.5,
metalness: 0, metalness: 0,
}) })
@@ -37,27 +23,6 @@ const glassMaterial = new MeshStandardNodeMaterial({
depthWrite: false, depthWrite: false,
}) })
const thresholdMaterial = new MeshStandardNodeMaterial({
name: 'door-threshold',
color: '#999',
roughness: 0.4,
metalness: 0.5,
})
const handleMaterial = new MeshStandardNodeMaterial({
name: 'door-handle',
color: '#bbb',
roughness: 0.2,
metalness: 0.8,
})
const closerMaterial = new MeshStandardNodeMaterial({
name: 'door-closer',
color: '#333',
roughness: 0.4,
metalness: 0.3,
})
// Invisible material for root mesh — used as selection hitbox only // Invisible material for root mesh — used as selection hitbox only
const hitboxMaterial = new THREE.MeshBasicMaterial({ visible: false }) const hitboxMaterial = new THREE.MeshBasicMaterial({ visible: false })
@@ -121,7 +86,7 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
const { const {
width, height, frameThickness, frameDepth, threshold, thresholdHeight, width, height, frameThickness, frameDepth, threshold, thresholdHeight,
segments, handle, handleHeight, handleSide, segments, handle, handleHeight, handleSide,
doorCloser, panicBar, panicBarHeight, contentPadding, doorCloser, panicBar, panicBarHeight, contentPadding, hingesSide,
} = node } = node
// Leaf occupies the full opening (no bottom frame bar — door opens to floor) // Leaf occupies the full opening (no bottom frame bar — door opens to floor)
@@ -133,15 +98,15 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
// ── Frame members ── // ── Frame members ──
// Left post — full height // Left post — full height
addBox(mesh, frameMaterial, frameThickness, height, frameDepth, -width / 2 + frameThickness / 2, 0, 0) addBox(mesh, baseMaterial, frameThickness, height, frameDepth, -width / 2 + frameThickness / 2, 0, 0)
// Right post — full height // Right post — full height
addBox(mesh, frameMaterial, frameThickness, height, frameDepth, width / 2 - frameThickness / 2, 0, 0) addBox(mesh, baseMaterial, frameThickness, height, frameDepth, width / 2 - frameThickness / 2, 0, 0)
// Head (top bar) — full width // Head (top bar) — full width
addBox(mesh, frameMaterial, width, frameThickness, frameDepth, 0, height / 2 - frameThickness / 2, 0) addBox(mesh, baseMaterial, width, frameThickness, frameDepth, 0, height / 2 - frameThickness / 2, 0)
// ── Threshold (inside the frame) ── // ── Threshold (inside the frame) ──
if (threshold) { if (threshold) {
addBox(mesh, thresholdMaterial, leafW, thresholdHeight, frameDepth, 0, -height / 2 + thresholdHeight / 2, 0) addBox(mesh, baseMaterial, leafW, thresholdHeight, frameDepth, 0, -height / 2 + thresholdHeight / 2, 0)
} }
// ── Leaf — contentPadding border strips (no full backing; glass areas are open) ── // ── Leaf — contentPadding border strips (no full backing; glass areas are open) ──
@@ -149,16 +114,16 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
const cpY = contentPadding[1] const cpY = contentPadding[1]
if (cpY > 0) { if (cpY > 0) {
// Top strip // Top strip
addBox(mesh, leafMaterial, leafW, cpY, leafDepth, 0, leafCenterY + leafH / 2 - cpY / 2, 0) addBox(mesh, baseMaterial, leafW, cpY, leafDepth, 0, leafCenterY + leafH / 2 - cpY / 2, 0)
// Bottom strip // Bottom strip
addBox(mesh, leafMaterial, leafW, cpY, leafDepth, 0, leafCenterY - leafH / 2 + cpY / 2, 0) addBox(mesh, baseMaterial, leafW, cpY, leafDepth, 0, leafCenterY - leafH / 2 + cpY / 2, 0)
} }
if (cpX > 0) { if (cpX > 0) {
const innerH = leafH - 2 * cpY const innerH = leafH - 2 * cpY
// Left strip // Left strip
addBox(mesh, leafMaterial, cpX, innerH, leafDepth, -leafW / 2 + cpX / 2, leafCenterY, 0) addBox(mesh, baseMaterial, cpX, innerH, leafDepth, -leafW / 2 + cpX / 2, leafCenterY, 0)
// Right strip // Right strip
addBox(mesh, leafMaterial, cpX, innerH, leafDepth, leafW / 2 - cpX / 2, leafCenterY, 0) addBox(mesh, baseMaterial, cpX, innerH, leafDepth, leafW / 2 - cpX / 2, leafCenterY, 0)
} }
// Content area inside padding // Content area inside padding
@@ -192,7 +157,7 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
cx = -contentW / 2 cx = -contentW / 2
for (let c = 0; c < numCols - 1; c++) { for (let c = 0; c < numCols - 1; c++) {
cx += colWidths[c]! cx += colWidths[c]!
addBox(mesh, leafMaterial, seg.dividerThickness, segH, leafDepth + 0.001, cx + seg.dividerThickness / 2, segCenterY, 0) addBox(mesh, baseMaterial, seg.dividerThickness, segH, leafDepth + 0.001, cx + seg.dividerThickness / 2, segCenterY, 0)
cx += seg.dividerThickness cx += seg.dividerThickness
} }
@@ -207,18 +172,18 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
addBox(mesh, glassMaterial, colW, segH, glassDepth, colX, segCenterY, 0) addBox(mesh, glassMaterial, colW, segH, glassDepth, colX, segCenterY, 0)
} else if (seg.type === 'panel') { } else if (seg.type === 'panel') {
// Opaque leaf backing for this column // Opaque leaf backing for this column
addBox(mesh, leafMaterial, colW, segH, leafDepth, colX, segCenterY, 0) addBox(mesh, baseMaterial, colW, segH, leafDepth, colX, segCenterY, 0)
// Raised panel detail // Raised panel detail
const panelW = colW - 2 * seg.panelInset const panelW = colW - 2 * seg.panelInset
const panelH = segH - 2 * seg.panelInset const panelH = segH - 2 * seg.panelInset
if (panelW > 0.01 && panelH > 0.01) { if (panelW > 0.01 && panelH > 0.01) {
const effectiveDepth = Math.abs(seg.panelDepth) < 0.002 ? 0.005 : Math.abs(seg.panelDepth) const effectiveDepth = Math.abs(seg.panelDepth) < 0.002 ? 0.005 : Math.abs(seg.panelDepth)
const panelZ = leafDepth / 2 + effectiveDepth / 2 const panelZ = leafDepth / 2 + effectiveDepth / 2
addBox(mesh, panelMaterial, panelW, panelH, effectiveDepth, colX, segCenterY, panelZ) addBox(mesh, baseMaterial, panelW, panelH, effectiveDepth, colX, segCenterY, panelZ)
} }
} else { } else {
// 'empty' — opaque backing, no detail // 'empty' — opaque backing, no detail
addBox(mesh, leafMaterial, colW, segH, leafDepth, colX, segCenterY, 0) addBox(mesh, baseMaterial, colW, segH, leafDepth, colX, segCenterY, 0)
} }
} }
@@ -238,24 +203,41 @@ function updateDoorMesh(node: DoorNode, mesh: THREE.Mesh) {
: -leafW / 2 + 0.045 : -leafW / 2 + 0.045
// Backplate // Backplate
addBox(mesh, handleMaterial, 0.028, 0.14, 0.01, handleX, handleY, faceZ + 0.005) addBox(mesh, baseMaterial, 0.028, 0.14, 0.01, handleX, handleY, faceZ + 0.005)
// Grip lever // Grip lever
addBox(mesh, handleMaterial, 0.022, 0.1, 0.035, handleX, handleY, faceZ + 0.025) addBox(mesh, baseMaterial, 0.022, 0.1, 0.035, handleX, handleY, faceZ + 0.025)
} }
// ── Door closer (commercial hardware at top) ── // ── Door closer (commercial hardware at top) ──
if (doorCloser) { if (doorCloser) {
const closerY = leafCenterY + leafH / 2 - 0.04 const closerY = leafCenterY + leafH / 2 - 0.04
// Body // Body
addBox(mesh, closerMaterial, 0.28, 0.055, 0.055, 0, closerY, leafDepth / 2 + 0.03) addBox(mesh, baseMaterial, 0.28, 0.055, 0.055, 0, closerY, leafDepth / 2 + 0.03)
// Arm (simplified as thin bar to frame side) // Arm (simplified as thin bar to frame side)
addBox(mesh, closerMaterial, 0.14, 0.015, 0.015, leafW / 4, closerY + 0.025, leafDepth / 2 + 0.015) addBox(mesh, baseMaterial, 0.14, 0.015, 0.015, leafW / 4, closerY + 0.025, leafDepth / 2 + 0.015)
} }
// ── Panic bar ── // ── Panic bar ──
if (panicBar) { if (panicBar) {
const barY = panicBarHeight - height / 2 const barY = panicBarHeight - height / 2
addBox(mesh, handleMaterial, leafW * 0.72, 0.04, 0.055, 0, barY, leafDepth / 2 + 0.03) addBox(mesh, baseMaterial, leafW * 0.72, 0.04, 0.055, 0, barY, leafDepth / 2 + 0.03)
}
// ── Hinges (3 knuckle-style hinges on the hinge side) ──
{
const hingeX = hingesSide === 'right'
? leafW / 2 - 0.012
: -leafW / 2 + 0.012
const hingeZ = 0 // centered in leaf depth
const hingeH = 0.1
const hingeW = 0.024
const hingeD = leafDepth + 0.016
// Bottom hinge ~0.25m from floor, middle hinge, top hinge ~0.25m from top
const leafBottom = leafCenterY - leafH / 2
const leafTop = leafCenterY + leafH / 2
addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeX, leafBottom + 0.25, hingeZ)
addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeX, (leafBottom + leafTop) / 2, hingeZ)
addBox(mesh, baseMaterial, hingeW, hingeH, hingeD, hingeX, leafTop - 0.25, hingeZ)
} }
// ── Cutout (for wall CSG) — always full door dimensions, 1m deep ── // ── Cutout (for wall CSG) — always full door dimensions, 1m deep ──
@@ -12,10 +12,11 @@ export const WallRenderer = ({ node }: { node: WallNode }) => {
const handlers = useNodeEvents(node, 'wall') const handlers = useNodeEvents(node, 'wall')
return ( return (
<mesh ref={ref} castShadow receiveShadow visible={node.visible}> <mesh ref={ref} castShadow receiveShadow visible={node.visible} {...handlers}>
{/* WallSystem will replace this geometry in the next frame */} {/* WallSystem will replace this geometry in the next frame */}
<boxGeometry args={[0, 0, 0]} /> <boxGeometry args={[0, 0, 0]} />
<mesh name="collision-mesh" {...handlers} visible={false}> {/* Collision mesh: full-wall geometry (no cutouts) for WallSystem, no event handlers */}
<mesh name="collision-mesh" visible={false}>
<boxGeometry args={[0, 0, 0]} /> <boxGeometry args={[0, 0, 0]} />
</mesh> </mesh>