window panel
This commit is contained in:
@@ -280,13 +280,15 @@ export const WindowTool: React.FC = () => {
|
|||||||
frameDepth: draft.frameDepth,
|
frameDepth: draft.frameDepth,
|
||||||
columnRatios: draft.columnRatios,
|
columnRatios: draft.columnRatios,
|
||||||
rowRatios: draft.rowRatios,
|
rowRatios: draft.rowRatios,
|
||||||
dividerThickness: draft.dividerThickness,
|
columnDividerThickness: draft.columnDividerThickness,
|
||||||
|
rowDividerThickness: draft.rowDividerThickness,
|
||||||
sill: draft.sill,
|
sill: draft.sill,
|
||||||
sillDepth: draft.sillDepth,
|
sillDepth: draft.sillDepth,
|
||||||
sillThickness: draft.sillThickness,
|
sillThickness: draft.sillThickness,
|
||||||
})
|
})
|
||||||
|
|
||||||
useScene.getState().createNode(node, event.node.id as AnyNodeId)
|
useScene.getState().createNode(node, event.node.id as AnyNodeId)
|
||||||
|
useViewer.getState().setSelection({ selectedIds: [node.id] })
|
||||||
useScene.temporal.getState().pause()
|
useScene.temporal.getState().pause()
|
||||||
|
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { type AnyNode, type AnyNodeId, type WindowNode, useScene } from '@pascal-app/core'
|
import { type AnyNode, type AnyNodeId, type WindowNode, useScene } from '@pascal-app/core'
|
||||||
import { useViewer } from '@pascal-app/viewer'
|
import { useViewer } from '@pascal-app/viewer'
|
||||||
import { X } from 'lucide-react'
|
import { FlipHorizontal2, X } from 'lucide-react'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { NumberInput } from '@/components/ui/primitives/number-input'
|
import { NumberInput } from '@/components/ui/primitives/number-input'
|
||||||
@@ -23,7 +23,6 @@ export function WindowPanel() {
|
|||||||
(updates: Partial<WindowNode>) => {
|
(updates: Partial<WindowNode>) => {
|
||||||
if (!selectedId) return
|
if (!selectedId) return
|
||||||
updateNode(selectedId as AnyNode['id'], updates)
|
updateNode(selectedId as AnyNode['id'], updates)
|
||||||
// Mark dirty so window-system regenerates geometry
|
|
||||||
useScene.getState().dirtyNodes.add(selectedId as AnyNodeId)
|
useScene.getState().dirtyNodes.add(selectedId as AnyNodeId)
|
||||||
},
|
},
|
||||||
[selectedId, updateNode],
|
[selectedId, updateNode],
|
||||||
@@ -33,13 +32,53 @@ export function WindowPanel() {
|
|||||||
setSelection({ selectedIds: [] })
|
setSelection({ selectedIds: [] })
|
||||||
}, [setSelection])
|
}, [setSelection])
|
||||||
|
|
||||||
|
const handleFlip = useCallback(() => {
|
||||||
|
if (!node) return
|
||||||
|
handleUpdate({
|
||||||
|
side: node.side === 'front' ? 'back' : 'front',
|
||||||
|
rotation: [node.rotation[0], node.rotation[1] + Math.PI, node.rotation[2]],
|
||||||
|
})
|
||||||
|
}, [node, handleUpdate])
|
||||||
|
|
||||||
if (!node || node.type !== 'window' || selectedIds.length !== 1) return null
|
if (!node || node.type !== 'window' || selectedIds.length !== 1) return null
|
||||||
|
|
||||||
const columns = node.columnRatios.length
|
const numCols = node.columnRatios.length
|
||||||
const rows = node.rowRatios.length
|
const numRows = node.rowRatios.length
|
||||||
|
|
||||||
|
// Normalized ratios (always sum to 1 for display)
|
||||||
|
const colSum = node.columnRatios.reduce((a, b) => a + b, 0)
|
||||||
|
const rowSum = node.rowRatios.reduce((a, b) => a + b, 0)
|
||||||
|
const normCols = node.columnRatios.map(r => r / colSum)
|
||||||
|
const normRows = node.rowRatios.map(r => r / rowSum)
|
||||||
|
|
||||||
|
const setColumnRatio = (index: number, newVal: number) => {
|
||||||
|
const clamped = Math.max(0.05, Math.min(0.95, newVal))
|
||||||
|
const neighborIdx = index < numCols - 1 ? index + 1 : index - 1
|
||||||
|
const delta = clamped - normCols[index]!
|
||||||
|
const neighborVal = Math.max(0.05, normCols[neighborIdx]! - delta)
|
||||||
|
const newRatios = normCols.map((v, i) => {
|
||||||
|
if (i === index) return clamped
|
||||||
|
if (i === neighborIdx) return neighborVal
|
||||||
|
return v
|
||||||
|
})
|
||||||
|
handleUpdate({ columnRatios: newRatios })
|
||||||
|
}
|
||||||
|
|
||||||
|
const setRowRatio = (index: number, newVal: number) => {
|
||||||
|
const clamped = Math.max(0.05, Math.min(0.95, newVal))
|
||||||
|
const neighborIdx = index < numRows - 1 ? index + 1 : index - 1
|
||||||
|
const delta = clamped - normRows[index]!
|
||||||
|
const neighborVal = Math.max(0.05, normRows[neighborIdx]! - delta)
|
||||||
|
const newRatios = normRows.map((v, i) => {
|
||||||
|
if (i === index) return clamped
|
||||||
|
if (i === neighborIdx) return neighborVal
|
||||||
|
return v
|
||||||
|
})
|
||||||
|
handleUpdate({ rowRatios: newRatios })
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pointer-events-auto fixed top-20 right-4 z-50 flex w-72 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">
|
||||||
{/* 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">
|
||||||
@@ -59,6 +98,36 @@ export function WindowPanel() {
|
|||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="flex-1 overflow-y-auto p-3 space-y-4">
|
<div className="flex-1 overflow-y-auto p-3 space-y-4">
|
||||||
|
|
||||||
|
{/* Position */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||||
|
Position
|
||||||
|
</label>
|
||||||
|
<div className="grid grid-cols-2 gap-2">
|
||||||
|
<NumberInput
|
||||||
|
label="X"
|
||||||
|
value={Math.round(node.position[0] * 100) / 100}
|
||||||
|
onChange={(v) => handleUpdate({ position: [v, node.position[1], node.position[2]] })}
|
||||||
|
precision={2}
|
||||||
|
/>
|
||||||
|
<NumberInput
|
||||||
|
label="Y"
|
||||||
|
value={Math.round(node.position[1] * 100) / 100}
|
||||||
|
onChange={(v) => handleUpdate({ position: [node.position[0], v, node.position[2]] })}
|
||||||
|
precision={2}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="w-full flex items-center justify-center gap-1.5 rounded border border-border px-2 py-1.5 text-xs hover:bg-accent cursor-pointer"
|
||||||
|
onClick={handleFlip}
|
||||||
|
>
|
||||||
|
<FlipHorizontal2 className="h-3.5 w-3.5" />
|
||||||
|
Flip Side
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Dimensions */}
|
{/* Dimensions */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
<label className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
|
||||||
@@ -68,7 +137,7 @@ export function WindowPanel() {
|
|||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label="Width"
|
label="Width"
|
||||||
value={Math.round(node.width * 1000) / 1000}
|
value={Math.round(node.width * 100) / 100}
|
||||||
onChange={(v) => handleUpdate({ width: v })}
|
onChange={(v) => handleUpdate({ width: v })}
|
||||||
min={0.2}
|
min={0.2}
|
||||||
precision={2}
|
precision={2}
|
||||||
@@ -79,7 +148,7 @@ export function WindowPanel() {
|
|||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label="Height"
|
label="Height"
|
||||||
value={Math.round(node.height * 1000) / 1000}
|
value={Math.round(node.height * 100) / 100}
|
||||||
onChange={(v) => handleUpdate({ height: v })}
|
onChange={(v) => handleUpdate({ height: v })}
|
||||||
min={0.2}
|
min={0.2}
|
||||||
precision={2}
|
precision={2}
|
||||||
@@ -103,6 +172,7 @@ export function WindowPanel() {
|
|||||||
onChange={(v) => handleUpdate({ frameThickness: v })}
|
onChange={(v) => handleUpdate({ frameThickness: v })}
|
||||||
min={0.01}
|
min={0.01}
|
||||||
precision={3}
|
precision={3}
|
||||||
|
step={0.01}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
||||||
@@ -114,6 +184,7 @@ export function WindowPanel() {
|
|||||||
onChange={(v) => handleUpdate({ frameDepth: v })}
|
onChange={(v) => handleUpdate({ frameDepth: v })}
|
||||||
min={0.01}
|
min={0.01}
|
||||||
precision={3}
|
precision={3}
|
||||||
|
step={0.01}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
||||||
@@ -127,47 +198,98 @@ export function WindowPanel() {
|
|||||||
Grid
|
Grid
|
||||||
</label>
|
</label>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label="Columns"
|
label="Columns"
|
||||||
value={columns}
|
value={numCols}
|
||||||
onChange={(v) => {
|
onChange={(v) => {
|
||||||
const n = Math.max(1, Math.min(8, Math.round(v)))
|
const n = Math.max(1, Math.min(8, Math.round(v)))
|
||||||
handleUpdate({ columnRatios: Array(n).fill(1) })
|
handleUpdate({ columnRatios: Array(n).fill(1 / n) })
|
||||||
}}
|
}}
|
||||||
min={1}
|
min={1}
|
||||||
max={8}
|
max={8}
|
||||||
precision={0}
|
precision={0}
|
||||||
className="flex-1"
|
step={1}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-1.5">
|
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label="Rows"
|
label="Rows"
|
||||||
value={rows}
|
value={numRows}
|
||||||
onChange={(v) => {
|
onChange={(v) => {
|
||||||
const n = Math.max(1, Math.min(8, Math.round(v)))
|
const n = Math.max(1, Math.min(8, Math.round(v)))
|
||||||
handleUpdate({ rowRatios: Array(n).fill(1) })
|
handleUpdate({ rowRatios: Array(n).fill(1 / n) })
|
||||||
}}
|
}}
|
||||||
min={1}
|
min={1}
|
||||||
max={8}
|
max={8}
|
||||||
precision={0}
|
precision={0}
|
||||||
className="flex-1"
|
step={1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Column ratios */}
|
||||||
|
{numCols > 1 && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<span className="text-muted-foreground text-xs">Column widths</span>
|
||||||
|
{normCols.map((ratio, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-1.5">
|
||||||
|
<NumberInput
|
||||||
|
label={`C${i + 1}`}
|
||||||
|
value={Math.round(ratio * 100 * 10) / 10}
|
||||||
|
onChange={(v) => setColumnRatio(i, v / 100)}
|
||||||
|
min={5}
|
||||||
|
max={95}
|
||||||
|
precision={1}
|
||||||
|
step={1}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<span className="text-muted-foreground text-xs shrink-0">%</span>
|
||||||
</div>
|
</div>
|
||||||
{(columns > 1 || rows > 1) && (
|
))}
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label="Divider thickness"
|
label="Col divider"
|
||||||
value={Math.round(node.dividerThickness * 1000) / 1000}
|
value={Math.round((node.columnDividerThickness ?? 0.03) * 1000) / 1000}
|
||||||
onChange={(v) => handleUpdate({ dividerThickness: v })}
|
onChange={(v) => handleUpdate({ columnDividerThickness: v })}
|
||||||
min={0.005}
|
min={0.005}
|
||||||
precision={3}
|
precision={3}
|
||||||
|
step={0.01}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Row ratios */}
|
||||||
|
{numRows > 1 && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<span className="text-muted-foreground text-xs">Row heights</span>
|
||||||
|
{normRows.map((ratio, i) => (
|
||||||
|
<div key={i} className="flex items-center gap-1.5">
|
||||||
|
<NumberInput
|
||||||
|
label={`R${i + 1}`}
|
||||||
|
value={Math.round(ratio * 100 * 10) / 10}
|
||||||
|
onChange={(v) => setRowRatio(i, v / 100)}
|
||||||
|
min={5}
|
||||||
|
max={95}
|
||||||
|
precision={1}
|
||||||
|
step={1}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<span className="text-muted-foreground text-xs shrink-0">%</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="flex items-center gap-1.5">
|
||||||
|
<NumberInput
|
||||||
|
label="Row divider"
|
||||||
|
value={Math.round((node.rowDividerThickness ?? 0.03) * 1000) / 1000}
|
||||||
|
onChange={(v) => handleUpdate({ rowDividerThickness: v })}
|
||||||
|
min={0.005}
|
||||||
|
precision={3}
|
||||||
|
step={0.01}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -191,6 +313,7 @@ export function WindowPanel() {
|
|||||||
onChange={(v) => handleUpdate({ sillDepth: v })}
|
onChange={(v) => handleUpdate({ sillDepth: v })}
|
||||||
min={0.01}
|
min={0.01}
|
||||||
precision={3}
|
precision={3}
|
||||||
|
step={0.01}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
||||||
@@ -202,6 +325,7 @@ export function WindowPanel() {
|
|||||||
onChange={(v) => handleUpdate({ sillThickness: v })}
|
onChange={(v) => handleUpdate({ sillThickness: v })}
|
||||||
min={0.005}
|
min={0.005}
|
||||||
precision={3}
|
precision={3}
|
||||||
|
step={0.01}
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
/>
|
/>
|
||||||
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
<span className="text-muted-foreground text-xs shrink-0">m</span>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ interface NumberInputProps {
|
|||||||
min?: number
|
min?: number
|
||||||
max?: number
|
max?: number
|
||||||
precision?: number
|
precision?: number
|
||||||
|
step?: number
|
||||||
className?: string
|
className?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export function NumberInput({
|
|||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
precision = 2,
|
precision = 2,
|
||||||
|
step = 0.1,
|
||||||
className = '',
|
className = '',
|
||||||
}: NumberInputProps) {
|
}: NumberInputProps) {
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
@@ -55,14 +57,14 @@ export function NumberInput({
|
|||||||
const deltaX = moveEvent.clientX - startXRef.current
|
const deltaX = moveEvent.clientX - startXRef.current
|
||||||
|
|
||||||
// Determine step size based on modifier keys
|
// Determine step size based on modifier keys
|
||||||
let step = 0.1 // Default
|
let dragStep = step // Default from prop
|
||||||
if (moveEvent.shiftKey) {
|
if (moveEvent.shiftKey) {
|
||||||
step = 1.0 // Coarse
|
dragStep = step * 10 // Coarse
|
||||||
} else if (moveEvent.altKey) {
|
} else if (moveEvent.altKey) {
|
||||||
step = 0.01 // Fine
|
dragStep = step * 0.1 // Fine
|
||||||
}
|
}
|
||||||
|
|
||||||
const deltaValue = deltaX * step
|
const deltaValue = deltaX * dragStep
|
||||||
const newValue = clamp(startValueRef.current + deltaValue)
|
const newValue = clamp(startValueRef.current + deltaValue)
|
||||||
const newFinalValue = Number.parseFloat(newValue.toFixed(precision))
|
const newFinalValue = Number.parseFloat(newValue.toFixed(precision))
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export const WindowNode = BaseNode.extend({
|
|||||||
// [1] = single pane (no division)
|
// [1] = single pane (no division)
|
||||||
columnRatios: z.array(z.number()).default([1]),
|
columnRatios: z.array(z.number()).default([1]),
|
||||||
rowRatios: z.array(z.number()).default([1]),
|
rowRatios: z.array(z.number()).default([1]),
|
||||||
dividerThickness: z.number().default(0.03),
|
columnDividerThickness: z.number().default(0.03),
|
||||||
|
rowDividerThickness: z.number().default(0.03),
|
||||||
|
|
||||||
// Sill
|
// Sill
|
||||||
sill: z.boolean().default(true),
|
sill: z.boolean().default(true),
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ function updateWindowMesh(node: WindowNode, mesh: THREE.Mesh) {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
width, height, frameDepth, frameThickness,
|
width, height, frameDepth, frameThickness,
|
||||||
columnRatios, rowRatios, dividerThickness,
|
columnRatios, rowRatios, columnDividerThickness, rowDividerThickness,
|
||||||
sill, sillDepth, sillThickness,
|
sill, sillDepth, sillThickness,
|
||||||
} = node
|
} = node
|
||||||
|
|
||||||
@@ -104,8 +104,8 @@ function updateWindowMesh(node: WindowNode, mesh: THREE.Mesh) {
|
|||||||
const numCols = columnRatios.length
|
const numCols = columnRatios.length
|
||||||
const numRows = rowRatios.length
|
const numRows = rowRatios.length
|
||||||
|
|
||||||
const usableW = innerW - (numCols - 1) * dividerThickness
|
const usableW = innerW - (numCols - 1) * columnDividerThickness
|
||||||
const usableH = innerH - (numRows - 1) * dividerThickness
|
const usableH = innerH - (numRows - 1) * rowDividerThickness
|
||||||
|
|
||||||
const colSum = columnRatios.reduce((a, b) => a + b, 0)
|
const colSum = columnRatios.reduce((a, b) => a + b, 0)
|
||||||
const rowSum = rowRatios.reduce((a, b) => a + b, 0)
|
const rowSum = rowRatios.reduce((a, b) => a + b, 0)
|
||||||
@@ -118,35 +118,35 @@ function updateWindowMesh(node: WindowNode, mesh: THREE.Mesh) {
|
|||||||
for (let c = 0; c < numCols; c++) {
|
for (let c = 0; c < numCols; c++) {
|
||||||
colXCenters.push(cx + colWidths[c]! / 2)
|
colXCenters.push(cx + colWidths[c]! / 2)
|
||||||
cx += colWidths[c]!
|
cx += colWidths[c]!
|
||||||
if (c < numCols - 1) cx += dividerThickness
|
if (c < numCols - 1) cx += columnDividerThickness
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute row y-centers starting from bottom edge of inner area
|
// Compute row y-centers starting from top edge of inner area (R1 = top)
|
||||||
const rowYCenters: number[] = []
|
const rowYCenters: number[] = []
|
||||||
let cy = -innerH / 2
|
let cy = innerH / 2
|
||||||
for (let r = 0; r < numRows; r++) {
|
for (let r = 0; r < numRows; r++) {
|
||||||
rowYCenters.push(cy + rowHeights[r]! / 2)
|
rowYCenters.push(cy - rowHeights[r]! / 2)
|
||||||
cy += rowHeights[r]!
|
cy -= rowHeights[r]!
|
||||||
if (r < numRows - 1) cy += dividerThickness
|
if (r < numRows - 1) cy -= rowDividerThickness
|
||||||
}
|
}
|
||||||
|
|
||||||
// Column dividers — full inner height
|
// Column dividers — full inner height
|
||||||
cx = -innerW / 2
|
cx = -innerW / 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, frameMaterial, dividerThickness, innerH, frameDepth, cx + dividerThickness / 2, 0, 0)
|
addBox(mesh, frameMaterial, columnDividerThickness, innerH, frameDepth, cx + columnDividerThickness / 2, 0, 0)
|
||||||
cx += dividerThickness
|
cx += columnDividerThickness
|
||||||
}
|
}
|
||||||
|
|
||||||
// Row dividers — per column width, so they don't overlap column dividers
|
// Row dividers — per column width, so they don't overlap column dividers (top to bottom)
|
||||||
cy = -innerH / 2
|
cy = innerH / 2
|
||||||
for (let r = 0; r < numRows - 1; r++) {
|
for (let r = 0; r < numRows - 1; r++) {
|
||||||
cy += rowHeights[r]!
|
cy -= rowHeights[r]!
|
||||||
const divY = cy + dividerThickness / 2
|
const divY = cy - rowDividerThickness / 2
|
||||||
for (let c = 0; c < numCols; c++) {
|
for (let c = 0; c < numCols; c++) {
|
||||||
addBox(mesh, frameMaterial, colWidths[c]!, dividerThickness, frameDepth, colXCenters[c]!, divY, 0)
|
addBox(mesh, frameMaterial, colWidths[c]!, rowDividerThickness, frameDepth, colXCenters[c]!, divY, 0)
|
||||||
}
|
}
|
||||||
cy += dividerThickness
|
cy -= rowDividerThickness
|
||||||
}
|
}
|
||||||
|
|
||||||
// Glass panes
|
// Glass panes
|
||||||
|
|||||||
Reference in New Issue
Block a user