import { type ColumnNode, useLiveTransforms, useRegistry } from '@pascal-app/core' import { createContext, useContext, useMemo, useRef } from 'react' import type { Group, Material } from 'three' import { useNodeEvents } from '../../../hooks/use-node-events' import { baseMaterial, createMaterial, createMaterialFromPresetRef } from '../../../lib/materials' import { createColumnBoxGeometry, createColumnCylinderGeometry, createColumnSphereGeometry, createColumnTorusGeometry, } from '../../../systems/column/column-geometry' const ColumnMaterialContext = createContext(baseMaterial as Material) const ColumnEdgeSoftnessContext = createContext(0.025) function ColumnMaterial() { const material = useContext(ColumnMaterialContext) return } function createColumnMaterial({ material, materialPreset, }: Pick) { const presetMaterial = createMaterialFromPresetRef(materialPreset) if (presetMaterial) return presetMaterial if (material) return createMaterial(material) return baseMaterial } function getSegments(node: ColumnNode) { if (node.crossSection === 'octagonal') return 8 if (node.crossSection === 'sixteen-sided') return 16 return 32 } function getShaftProfile(node: ColumnNode) { return node.shaftProfile ?? (node.shaftTaper > 0 ? 'tapered' : 'straight') } function getShaftSegmentCount(node: ColumnNode) { const shaftProfile = getShaftProfile(node) const shaftTaper = node.shaftTaper ?? 0 const hasTwist = Math.abs(node.shaftTwistStep ?? 0) > 0.001 return Math.max( hasTwist ? 4 : 1, shaftProfile === 'straight' && shaftTaper <= 0 && !hasTwist ? 1 : (node.shaftSegmentCount ?? (hasTwist ? 12 : 24)), ) } function getShaftTwistRadians(node: ColumnNode, index: number) { return ((node.shaftTwistStep ?? 0) * Math.PI * index) / 180 } function getShaftScaleAt(node: ColumnNode, t: number) { const shaftProfile = getShaftProfile(node) const shaftTaper = Math.min(node.shaftTaper ?? 0, 0.85) const startScale = node.shaftStartScale ?? 0.72 const endScale = node.shaftEndScale ?? startScale const shaftBulge = node.shaftBulge ?? (shaftProfile === 'bulged' ? 0.16 : shaftProfile === 'baluster' ? 0.2 : shaftProfile === 'hourglass' ? 0.18 : 0) const taperedScale = 1 - shaftTaper * t const linearScale = (startScale + (endScale - startScale) * t) * taperedScale const bulgeCurve = Math.sin(Math.PI * t) const hourglassCurve = Math.abs(t - 0.5) * 2 const profileScale = shaftProfile === 'bulged' || shaftProfile === 'baluster' ? linearScale + shaftBulge * bulgeCurve : shaftProfile === 'hourglass' ? linearScale - shaftBulge * (1 - hourglassCurve) : linearScale return Math.max(0.1, profileScale) } type VectorTuple = [number, number, number] function MappedBox({ depth, height, position, rotation, softenEdges = true, width, }: { depth: number height: number position: VectorTuple rotation?: VectorTuple softenEdges?: boolean width: number }) { const edgeSoftness = useContext(ColumnEdgeSoftnessContext) const minDimension = Math.max(0, Math.min(width, height, depth)) const bevelRadius = softenEdges ? Math.min(Math.max(0, edgeSoftness), minDimension * 0.35) : 0 const geometry = useMemo(() => { if (height <= 0 || width <= 0 || depth <= 0) return null return createColumnBoxGeometry(width, height, depth, bevelRadius) }, [bevelRadius, depth, height, width]) if (!geometry) return null return ( ) } function MappedCylinder({ height, position, radius, radiusBottom = radius, radiusTop = radius, radiusX = 1, radiusZ = 1, rotation, segments = 32, }: { height: number position: VectorTuple radius: number radiusBottom?: number radiusTop?: number radiusX?: number radiusZ?: number rotation?: VectorTuple segments?: number }) { const geometry = useMemo(() => { if (height <= 0 || radius <= 0 || radiusBottom < 0 || radiusTop < 0) return null return createColumnCylinderGeometry({ height, radiusBottom, radiusTop, radiusX, radiusZ, segments, }) }, [height, radius, radiusBottom, radiusTop, radiusX, radiusZ, segments]) if (!geometry) return null return ( ) } function MappedCone({ height, position, radiusX, radiusZ = radiusX, rotation, segments = 6, }: { height: number position: VectorTuple radiusX: number radiusZ?: number rotation?: VectorTuple segments?: number }) { const geometry = useMemo(() => { if (height <= 0 || radiusX <= 0 || radiusZ <= 0) return null return createColumnCylinderGeometry({ height, radiusBottom: 1, radiusTop: 0, radiusX, radiusZ, segments, }) }, [height, radiusX, radiusZ, segments]) if (!geometry) return null return ( ) } function MappedSphere({ position, radius, segments = 10, verticalSegments = 8, }: { position: VectorTuple radius: number segments?: number verticalSegments?: number }) { const geometry = useMemo(() => { if (radius <= 0) return null return createColumnSphereGeometry(radius, segments, verticalSegments) }, [radius, segments, verticalSegments]) if (!geometry) return null return ( ) } function MappedTorus({ arc, position, ringRadius, rotation, scaleX, scaleY, scaleZ, tubeRadius, }: { arc?: number position: VectorTuple ringRadius: number rotation?: VectorTuple scaleX?: number scaleY?: number scaleZ?: number tubeRadius: number }) { const geometry = useMemo(() => { if (ringRadius <= 0 || tubeRadius <= 0) return null return createColumnTorusGeometry({ arc, ringRadius, scaleX, scaleY, scaleZ, tubeRadius }) }, [arc, ringRadius, scaleX, scaleY, scaleZ, tubeRadius]) if (!geometry) return null return ( ) } function SquareBlock({ y, height, width, depth, softenEdges = true, }: { y: number height: number width: number depth: number softenEdges?: boolean }) { return ( ) } function RoundBlock({ x = 0, y, z = 0, height, radius, segments = 32, }: { x?: number y: number z?: number height: number radius: number segments?: number }) { return ( ) } function RoundedRectangleShaftSegment({ y, height, width, depth, cornerRadius, }: { y: number height: number width: number depth: number cornerRadius: number }) { if (height <= 0) return null const radius = Math.min(Math.max(0, cornerRadius), Math.min(width, depth) * 0.45) if (radius <= 0.001) { return } const innerWidth = Math.max(0, width - radius * 2) const innerDepth = Math.max(0, depth - radius * 2) const cornerX = width / 2 - radius const cornerZ = depth / 2 - radius return ( {innerWidth > 0 && ( )} {innerDepth > 0 && ( )} {( [ [cornerX, cornerZ], [cornerX, -cornerZ], [-cornerX, cornerZ], [-cornerX, -cornerZ], ] satisfies [number, number][] ).map(([x, z], index) => ( ))} ) } function OvalBlock({ y, height, width, depth, segments = 32, }: { y: number height: number width: number depth: number segments?: number }) { return ( ) } function ColumnBlock({ node, y, height, scale = 1, }: { node: ColumnNode y: number height: number scale?: number }) { if (height <= 0) return null const width = node.width * scale const depth = node.depth * scale const radius = node.radius * scale if (node.crossSection === 'square' || node.crossSection === 'rectangular') { return } return } function TaperedRoundShaft({ node, y, height }: { node: ColumnNode; y: number; height: number }) { const segmentCount = getShaftSegmentCount(node) const segmentHeight = height / segmentCount return ( {Array.from({ length: segmentCount }, (_, index) => { const t = (index + 0.5) / segmentCount const profileScale = getShaftScaleAt(node, t) return ( ) })} ) } function TaperedSquareShaft({ node, y, height }: { node: ColumnNode; y: number; height: number }) { const segmentCount = getShaftSegmentCount(node) const segmentHeight = height / segmentCount return ( {Array.from({ length: segmentCount }, (_, index) => { const t = (index + 0.5) / segmentCount const profileScale = getShaftScaleAt(node, t) return ( ) })} ) } function Shaft({ node, y, height }: { node: ColumnNode; y: number; height: number }) { if (height <= 0) return null if (node.style === 'cluster') { const sideRadius = Math.max(0.04, node.radius * 0.36) const offset = Math.max(node.radius * 0.78, node.width * 0.22) return ( {( [ [offset, 0], [-offset, 0], [0, offset], [0, -offset], ] satisfies [number, number][] ).map(([x, z], index) => ( ))} ) } if ( node.crossSection === 'round' || node.crossSection === 'octagonal' || node.crossSection === 'sixteen-sided' ) { return } return } function Base({ node, height }: { node: ColumnNode; height: number }) { if (height <= 0) return null const baseStyle = node.baseStyle ?? 'round-rings' const widthScale = node.baseWidthScale ?? 1.24 const depthScale = node.baseDepthScale ?? widthScale if (baseStyle === 'none') return null if (baseStyle === 'simple-square') { return ( ) } if (baseStyle === 'square-plinth') { return ( ) } if (baseStyle === 'stepped-square') { const tierCount = Math.max(3, node.baseTierCount ?? 3) const tierHeight = height / tierCount const stepSpread = node.baseStepSpread ?? 0.42 return ( {Array.from({ length: tierCount }, (_, index) => { const t = index / Math.max(1, tierCount - 1) const widthScaleAt = Math.max(0.5, widthScale - t * stepSpread) const depthScaleAt = Math.max(0.5, depthScale - t * stepSpread) return ( ) })} ) } if (baseStyle === 'round-rings') { const baseWidth = node.width * widthScale const baseDepth = node.depth * depthScale const plinthRatio = Math.min(0.7, Math.max(0.2, node.basePlinthHeightRatio ?? 0.44)) const plinthHeight = height * plinthRatio const roundedHeight = height - plinthHeight const bandHeight = roundedHeight * 0.57 const neckHeight = roundedHeight - bandHeight const bandScale = node.baseRoundBandScale ?? 0.92 const neckScale = node.baseNeckScale ?? 0.72 return ( ) } if (baseStyle === 'lotus' || baseStyle === 'ribbed-lotus') { const ribCount = node.baseRibCount ?? (baseStyle === 'ribbed-lotus' ? 24 : 14) const ribRadius = Math.max(0.01, node.width * 0.025) const baseRadius = Math.max(node.radius * widthScale, node.width * widthScale * 0.5) return ( {Array.from({ length: ribCount }, (_, index) => { const angle = (index / ribCount) * Math.PI * 2 return ( ) })} ) } if (baseStyle === 'panelled-pedestal') { const inset = node.basePanelInset ?? 0.02 return ( {( [ [0, node.depth * widthScale * 0.51, 0], [0, -node.depth * widthScale * 0.51, 0], [node.width * widthScale * 0.51, 0, Math.PI / 2], [-node.width * widthScale * 0.51, 0, Math.PI / 2], ] satisfies [number, number, number][] ).map(([x, z, rotation], index) => ( ))} ) } return } function BaseCarvings({ node, height }: { node: ColumnNode; height: number }) { const placement = node.carvingPlacement ?? 'capital' const carvingLevel = node.baseCarvingLevel ?? 0 if (carvingLevel <= 0 || height <= 0 || (placement !== 'base' && placement !== 'all')) { return null } const count = Math.max(8, carvingLevel * 8) const radius = Math.max(node.radius * 1.04, Math.max(node.width, node.depth) * 0.5) const y = height * 0.52 return ( {Array.from({ length: count }, (_, index) => { const angle = (index / count) * Math.PI * 2 return ( ) })} ) } function Rings({ node, shaftY, shaftHeight, }: { node: ColumnNode shaftY: number shaftHeight: number }) { if (node.ringCount <= 0 || shaftHeight <= 0) return null const ringPlacement = node.ringPlacement ?? 'ends' const ringSpread = Math.min(0.45, Math.max(0.04, node.ringSpread ?? 0.16)) const ringHeight = Math.min( node.ringThickness ?? 0.055, shaftHeight / Math.max(8, node.ringCount * 3), ) const rings = Array.from({ length: node.ringCount }, (_, index) => { const pairIndex = Math.floor(index / 2) const nearTop = index % 2 === 1 const pairCount = Math.ceil(node.ringCount / 2) const pairT = pairCount <= 1 ? 0 : pairIndex / (pairCount - 1) const offset = Math.min(0.48, 0.06 + pairT * Math.max(0, ringSpread - 0.06)) const oneSideT = 0.06 + (index / Math.max(1, node.ringCount - 1)) * Math.max(0, ringSpread - 0.06) const t = ringPlacement === 'even' ? (index + 1) / (node.ringCount + 1) : ringPlacement === 'top' ? 1 - Math.min(0.48, oneSideT) : ringPlacement === 'bottom' ? Math.min(0.48, oneSideT) : nearTop ? 1 - offset : offset return { scale: Math.min(1.4, getShaftScaleAt(node, t) + 0.12), y: shaftY + shaftHeight * t - ringHeight / 2, } }).sort((a, b) => a.y - b.y) return ( {rings.map((ring, index) => ( ))} ) } function LatheBands({ node, shaftY, shaftHeight, }: { node: ColumnNode shaftY: number shaftHeight: number }) { const latheRingCount = Math.max( node.latheRingCount ?? 0, node.shaftDetail === 'lathe-turned' ? 8 : 0, ) if (latheRingCount <= 0 || shaftHeight <= 0) return null const placement = node.latheRingSpacing ?? 'ends' const bandHeight = Math.min(0.04, shaftHeight / Math.max(12, latheRingCount * 3)) const bands = Array.from({ length: latheRingCount }, (_, index) => { const pairIndex = Math.floor(index / 2) const nearTop = index % 2 === 1 const offset = Math.min(0.48, 0.1 + pairIndex * 0.04) const t = placement === 'even' ? (index + 1) / (latheRingCount + 1) : placement === 'top' ? 1 - Math.min(0.48, 0.08 + index * 0.04) : placement === 'bottom' ? Math.min(0.48, 0.08 + index * 0.04) : nearTop ? 1 - offset : offset return shaftY + shaftHeight * t - bandHeight / 2 }).sort((a, b) => a - b) return ( {bands.map((y, index) => ( ))} ) } function Flutes({ node, shaftY, shaftHeight, }: { node: ColumnNode shaftY: number shaftHeight: number }) { const fluteCount = Math.max(node.fluteCount, node.shaftDetail === 'fluted' ? 16 : 0) if (fluteCount <= 0 || shaftHeight <= 0 || node.crossSection !== 'round') return null const fluteDepth = node.fluteDepth ?? 0.02 const fluteWidth = node.fluteWidth ?? fluteDepth const fluteRadius = Math.max(0.006, fluteWidth * 0.42) const shaftRadius = node.radius * 0.74 return ( {Array.from({ length: fluteCount }, (_, index) => { const angle = (index / fluteCount) * Math.PI * 2 const x = Math.cos(angle) * shaftRadius const z = Math.sin(angle) * shaftRadius return ( ) })} ) } function DravidianShaftPanels({ node, shaftY, shaftHeight, }: { node: ColumnNode shaftY: number shaftHeight: number }) { const panelCount = Math.max( node.panelCount ?? 0, node.style === 'dravidian-carved' || node.shaftDetail === 'panelled' ? 3 : 0, ) if (panelCount <= 0 || shaftHeight <= 0) return null const shaftWidth = node.width * 0.72 const shaftDepth = node.depth * 0.72 const panelHeight = Math.min(0.42, shaftHeight / Math.max(4, panelCount + 2)) const panelWidth = node.width * 0.26 const rail = Math.max(0.012, node.width * 0.028) const reliefDepth = Math.max(0.012, node.panelInsetDepth ?? node.width * 0.025) const rows = Array.from({ length: panelCount }, (_, index) => (index + 1) / (panelCount + 1)) const panelShape = node.panelShape ?? 'rectangle' const PanelFace = ({ position, rotation = 0, }: { position: [number, number, number] rotation?: number }) => ( {panelShape === 'diamond' && ( )} {panelShape === 'arched' && ( )} ) return ( {rows.map((t, rowIndex) => { const y = shaftY + shaftHeight * t return ( ) })} ) } function SpiralRibs({ node, shaftY, shaftHeight, }: { node: ColumnNode shaftY: number shaftHeight: number }) { const spiralRibCount = node.spiralRibCount ?? 0 const spiralTwist = node.spiralTwist ?? 0 const shaftTaper = node.shaftTaper ?? 0 const ribCountSetting = Math.max(spiralRibCount, node.shaftDetail === 'spiral' ? 12 : 0) if (ribCountSetting <= 0 || spiralTwist === 0 || shaftHeight <= 0) return null const ribCount = Math.min(ribCountSetting, 24) const stepCount = 28 const ribDistance = node.radius * 0.78 const ribWidth = Math.max(0.012, node.radius * 0.06) const segmentHeight = (shaftHeight / stepCount) * 1.18 const lean = spiralTwist > 0 ? -0.55 : 0.55 return ( {Array.from({ length: ribCount * stepCount }, (_, index) => { const ribIndex = index % ribCount const stepIndex = Math.floor(index / ribCount) const t = (stepIndex + 0.5) / stepCount const angle = (ribIndex / ribCount) * Math.PI * 2 + t * spiralTwist * Math.PI * 2 const taperScale = 1 - Math.min(shaftTaper, 0.85) * t return ( ) })} ) } function LowerCarvedBand({ node, shaftY, shaftHeight, }: { node: ColumnNode shaftY: number shaftHeight: number }) { const placement = node.carvingPlacement ?? 'capital' if ( !node.lowerBandEnabled || shaftHeight <= 0 || (placement !== 'shaft' && placement !== 'all') ) { return null } const bandHeight = Math.min(node.lowerBandHeight ?? 0.24, shaftHeight * 0.35) const y = shaftY + shaftHeight * 0.12 const level = Math.max(1, node.lowerBandCarvingLevel ?? 1) const count = Math.max(6, level * 6) const distance = Math.max(node.radius * 0.82, Math.max(node.width, node.depth) * 0.36) return ( {Array.from({ length: count }, (_, index) => { const angle = (index / count) * Math.PI * 2 return ( ) })} ) } function CapitalCarvings({ node, capitalY, capitalHeight, }: { node: ColumnNode capitalY: number capitalHeight: number }) { const placement = node.carvingPlacement ?? 'capital' const carvingLevel = Math.max(node.carvingLevel ?? 0, node.capitalCarvingLevel ?? 0) const bandSetting = node.capitalBandCount ?? 0 if ( (carvingLevel <= 0 && bandSetting <= 0) || capitalHeight <= 0 || (placement !== 'capital' && placement !== 'all') ) { return null } const level = Math.min(Math.max(carvingLevel, bandSetting > 0 ? 1 : 0), 4) const bandHeight = Math.min(0.035, capitalHeight / 8) const bandCount = Math.min(bandSetting > 0 ? bandSetting : level + 1, 16) const bands = Array.from({ length: bandCount }, (_, index) => { const t = (index + 1) / (bandCount + 1) return capitalY + capitalHeight * t - bandHeight / 2 }) if (node.crossSection === 'square' || node.crossSection === 'rectangular') { const dentilCount = Math.max(node.dentilCount ?? 0, level * 4, 4) const dentilHeight = Math.min(0.08, capitalHeight * 0.28) const dentilDepth = Math.min(0.08, Math.min(node.width, node.depth) * 0.16) const dentilWidth = Math.max(0.025, node.width / (dentilCount * 1.75)) const halfWidth = node.width * 0.56 const halfDepth = node.depth * 0.56 const y = capitalY + capitalHeight * 0.28 const xPositions = Array.from({ length: dentilCount }, (_, index) => { const t = dentilCount === 1 ? 0.5 : index / (dentilCount - 1) return -halfWidth + t * halfWidth * 2 }) const zPositions = Array.from({ length: dentilCount }, (_, index) => { const t = dentilCount === 1 ? 0.5 : index / (dentilCount - 1) return -halfDepth + t * halfDepth * 2 }) return ( {bands.map((bandY, index) => ( ))} {xPositions.map((x, index) => ( ))} {zPositions.map((z, index) => ( ))} ) } const beadCount = Math.max(node.beadCount ?? 0, 8, level * 8) const beadRadius = Math.max(0.012, Math.min(0.03, node.radius * 0.12)) const beadDistance = node.radius * 1.24 const beadY = capitalY + capitalHeight * 0.24 return ( {bands.map((bandY, index) => ( ))} {Array.from({ length: beadCount }, (_, index) => { const angle = (index / beadCount) * Math.PI * 2 return ( ) })} ) } function Volutes({ node, capitalY, capitalHeight, }: { node: ColumnNode capitalY: number capitalHeight: number }) { if (!['volute', 'ionic-volute'].includes(node.capitalStyle ?? 'simple') || capitalHeight <= 0) return null const y = capitalY + capitalHeight * 0.62 const radius = node.voluteSize ?? Math.min(0.085, Math.max(0.04, node.width * 0.12)) const x = node.width * 0.46 const z = node.depth * 0.7 const maxVolutes = Math.max(0, Math.min(node.voluteCount ?? 4, 8)) const volutes = [ { position: [x, y, z] as [number, number, number], rotation: [0, 0, 0] as [number, number, number], }, { position: [-x, y, z] as [number, number, number], rotation: [0, 0, 0] as [number, number, number], }, { position: [x, y, -z] as [number, number, number], rotation: [0, Math.PI, 0] as [number, number, number], }, { position: [-x, y, -z] as [number, number, number], rotation: [0, Math.PI, 0] as [number, number, number], }, { position: [z, y, x] as [number, number, number], rotation: [0, Math.PI / 2, 0] as [number, number, number], }, { position: [z, y, -x] as [number, number, number], rotation: [0, Math.PI / 2, 0] as [number, number, number], }, { position: [-z, y, x] as [number, number, number], rotation: [0, -Math.PI / 2, 0] as [number, number, number], }, { position: [-z, y, -x] as [number, number, number], rotation: [0, -Math.PI / 2, 0] as [number, number, number], }, ].slice(0, maxVolutes) return ( {volutes.map((volute, index) => ( ))} ) } function LeafCarvings({ node, capitalY, capitalHeight, }: { node: ColumnNode capitalY: number capitalHeight: number }) { if ( !['leaf-carved', 'corinthian-leaf'].includes(node.capitalStyle ?? 'simple') || capitalHeight <= 0 ) { return null } const leafCount = node.leafCount ?? (node.crossSection === 'round' ? 18 : 12) const distance = Math.max(node.radius * 1.05, Math.max(node.width, node.depth) * 0.48) const rowCount = Math.max(0, Math.min(node.leafRows ?? 2, 4)) const rows = Array.from({ length: rowCount }, (_, index) => ({ y: capitalY + capitalHeight * (0.3 + index * 0.16), scale: 0.28 - index * 0.04, offset: index % 2 === 0 ? 0 : Math.PI / leafCount, })) return ( {rows.flatMap((row, rowIndex) => Array.from({ length: leafCount }, (_, index) => { const angle = (index / leafCount) * Math.PI * 2 + row.offset return ( ) }), )} ) } function Capital({ node, y, height }: { node: ColumnNode; y: number; height: number }) { if (height <= 0) return null const capitalStyle = node.capitalStyle ?? 'simple' if (capitalStyle === 'none') return null if (capitalStyle === 'south-indian-bracket' || capitalStyle === 'wood-bracket') { const tierCount = Math.max(1, node.bracketTierCount ?? 3) const tierHeight = height / tierCount const bracketDepth = node.bracketDepth ?? 0.35 return ( {Array.from({ length: tierCount }, (_, index) => { const t = index / Math.max(1, tierCount - 1) const scale = (node.capitalWidthScale ?? 1.6) + t * 0.32 return ( ) })} {Array.from({ length: node.pendantCount ?? 0 }, (_, index) => { const count = Math.max(1, node.pendantCount ?? 0) const angle = (index / count) * Math.PI * 2 const distance = Math.max(node.width, node.depth) * 0.56 return ( ) })} ) } if (capitalStyle === 'rounded' || capitalStyle === 'doric') { const topWidth = node.width * (node.capitalWidthScale ?? 1.34) const topDepth = node.depth * (node.capitalDepthScale ?? node.capitalWidthScale ?? 1.34) return ( ) } if (capitalStyle === 'stepped') { const widthScale = node.capitalWidthScale ?? 1.46 const depthScale = node.capitalDepthScale ?? widthScale const tierCount = Math.max(3, node.capitalTierCount ?? 3) const tierHeight = height / tierCount const stepSpread = node.capitalStepSpread ?? 0.42 return ( {Array.from({ length: tierCount }, (_, index) => { const t = index / Math.max(1, tierCount - 1) const widthScaleAt = Math.max(0.5, widthScale - (1 - t) * stepSpread) const depthScaleAt = Math.max(0.5, depthScale - (1 - t) * stepSpread) return ( ) })} ) } if ( capitalStyle === 'volute' || capitalStyle === 'ionic-volute' || capitalStyle === 'leaf-carved' || capitalStyle === 'corinthian-leaf' ) { const topWidth = node.width * (node.capitalWidthScale ?? 1.46) const topDepth = node.depth * (node.capitalDepthScale ?? node.capitalWidthScale ?? 1.46) return ( ) } const widthScale = node.capitalWidthScale ?? (capitalStyle === 'simple-slab' ? 1.28 : 1.18) const depthScale = node.capitalDepthScale ?? widthScale if (node.crossSection === 'square' || node.crossSection === 'rectangular') { return ( ) } return ( ) } export const ColumnRenderer = ({ node }: { node: ColumnNode }) => { const ref = useRef(null!) const handlers = useNodeEvents(node, 'column') const liveTransform = useLiveTransforms((state) => state.get(node.id)) const material = useMemo( () => createColumnMaterial({ material: node.material, materialPreset: node.materialPreset }), [ node.material, node.material?.preset, node.material?.properties, node.material?.texture, node.materialPreset, ], ) useRegistry(node.id, node.type, ref) const shaftLayout = useMemo(() => { const baseHeight = node.baseStyle === 'none' ? 0 : Math.min(node.baseHeight, node.height * 0.4) const capitalHeight = node.capitalStyle === 'none' ? 0 : Math.min(node.capitalHeight, node.height * 0.4) const shaftHeight = Math.max(0.1, node.height - baseHeight - capitalHeight) return { baseHeight, capitalHeight, shaftY: baseHeight, shaftHeight } }, [node.baseHeight, node.baseStyle, node.capitalHeight, node.capitalStyle, node.height]) return ( ) }