Enable pillar duplication in floating action menu
This commit is contained in:
@@ -86,6 +86,7 @@ export const ColumnNode = BaseNode.extend({
|
|||||||
shaftStartScale: z.number().min(0.2).max(2).default(0.72),
|
shaftStartScale: z.number().min(0.2).max(2).default(0.72),
|
||||||
shaftEndScale: z.number().min(0.2).max(2).default(0.72),
|
shaftEndScale: z.number().min(0.2).max(2).default(0.72),
|
||||||
shaftSegmentCount: z.number().int().min(1).max(64).default(24),
|
shaftSegmentCount: z.number().int().min(1).max(64).default(24),
|
||||||
|
shaftTwistStep: z.number().min(-90).max(90).default(0),
|
||||||
shaftCornerRadius: z.number().min(0).max(0.3).default(0.035),
|
shaftCornerRadius: z.number().min(0).max(0.3).default(0.035),
|
||||||
shaftDetail: ColumnShaftDetail.default('none'),
|
shaftDetail: ColumnShaftDetail.default('none'),
|
||||||
baseStyle: ColumnBaseStyle.default('round-rings'),
|
baseStyle: ColumnBaseStyle.default('round-rings'),
|
||||||
@@ -144,6 +145,7 @@ export const ColumnNode = BaseNode.extend({
|
|||||||
- height/radius/width/depth: primary dimensions in meters
|
- height/radius/width/depth: primary dimensions in meters
|
||||||
- edgeSoftness: bevel radius for square/plinth/block edges
|
- edgeSoftness: bevel radius for square/plinth/block edges
|
||||||
- shaftProfile/shaftDetail: profile and surface treatment of the shaft
|
- shaftProfile/shaftDetail: profile and surface treatment of the shaft
|
||||||
|
- shaftTwistStep: per-segment shaft rotation in degrees
|
||||||
- shaftCornerRadius: rounded-corner radius for square/rectangular shaft cross-sections
|
- shaftCornerRadius: rounded-corner radius for square/rectangular shaft cross-sections
|
||||||
- baseStyle/capitalStyle: procedural base and top treatment with tier/detail controls
|
- baseStyle/capitalStyle: procedural base and top treatment with tier/detail controls
|
||||||
- baseHeight/capitalHeight: bottom and top block proportions
|
- baseHeight/capitalHeight: bottom and top block proportions
|
||||||
@@ -168,6 +170,7 @@ export const COLUMN_PRESETS = {
|
|||||||
shaftStartScale: 0.72,
|
shaftStartScale: 0.72,
|
||||||
shaftEndScale: 0.72,
|
shaftEndScale: 0.72,
|
||||||
shaftSegmentCount: 1,
|
shaftSegmentCount: 1,
|
||||||
|
shaftTwistStep: 0,
|
||||||
shaftCornerRadius: 0.035,
|
shaftCornerRadius: 0.035,
|
||||||
shaftDetail: 'none',
|
shaftDetail: 'none',
|
||||||
baseStyle: 'square-plinth',
|
baseStyle: 'square-plinth',
|
||||||
@@ -217,6 +220,7 @@ export const COLUMN_PRESETS = {
|
|||||||
shaftStartScale: 0.72,
|
shaftStartScale: 0.72,
|
||||||
shaftEndScale: 0.72,
|
shaftEndScale: 0.72,
|
||||||
shaftSegmentCount: 1,
|
shaftSegmentCount: 1,
|
||||||
|
shaftTwistStep: 0,
|
||||||
shaftCornerRadius: 0.045,
|
shaftCornerRadius: 0.045,
|
||||||
shaftDetail: 'none',
|
shaftDetail: 'none',
|
||||||
baseStyle: 'simple-square',
|
baseStyle: 'simple-square',
|
||||||
@@ -266,6 +270,7 @@ export const COLUMN_PRESETS = {
|
|||||||
shaftStartScale: 0.82,
|
shaftStartScale: 0.82,
|
||||||
shaftEndScale: 0.72,
|
shaftEndScale: 0.72,
|
||||||
shaftSegmentCount: 32,
|
shaftSegmentCount: 32,
|
||||||
|
shaftTwistStep: 0,
|
||||||
shaftCornerRadius: 0.035,
|
shaftCornerRadius: 0.035,
|
||||||
shaftDetail: 'none',
|
shaftDetail: 'none',
|
||||||
baseStyle: 'square-plinth',
|
baseStyle: 'square-plinth',
|
||||||
@@ -315,6 +320,7 @@ export const COLUMN_PRESETS = {
|
|||||||
shaftStartScale: 0.68,
|
shaftStartScale: 0.68,
|
||||||
shaftEndScale: 0.68,
|
shaftEndScale: 0.68,
|
||||||
shaftSegmentCount: 32,
|
shaftSegmentCount: 32,
|
||||||
|
shaftTwistStep: 0,
|
||||||
shaftCornerRadius: 0.035,
|
shaftCornerRadius: 0.035,
|
||||||
shaftDetail: 'none',
|
shaftDetail: 'none',
|
||||||
baseStyle: 'square-plinth',
|
baseStyle: 'square-plinth',
|
||||||
@@ -364,6 +370,7 @@ export const COLUMN_PRESETS = {
|
|||||||
shaftStartScale: 0.84,
|
shaftStartScale: 0.84,
|
||||||
shaftEndScale: 0.84,
|
shaftEndScale: 0.84,
|
||||||
shaftSegmentCount: 32,
|
shaftSegmentCount: 32,
|
||||||
|
shaftTwistStep: 0,
|
||||||
shaftCornerRadius: 0.035,
|
shaftCornerRadius: 0.035,
|
||||||
shaftDetail: 'none',
|
shaftDetail: 'none',
|
||||||
baseStyle: 'square-plinth',
|
baseStyle: 'square-plinth',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
type AnyNode,
|
type AnyNode,
|
||||||
type AnyNodeId,
|
type AnyNodeId,
|
||||||
type CeilingNode,
|
type CeilingNode,
|
||||||
|
ColumnNode,
|
||||||
DoorNode,
|
DoorNode,
|
||||||
FenceNode,
|
FenceNode,
|
||||||
generateId,
|
generateId,
|
||||||
@@ -262,6 +263,8 @@ export function FloatingActionMenu() {
|
|||||||
duplicate = WindowNode.parse(duplicateInfo)
|
duplicate = WindowNode.parse(duplicateInfo)
|
||||||
} else if (node.type === 'item') {
|
} else if (node.type === 'item') {
|
||||||
duplicate = ItemNode.parse(duplicateInfo)
|
duplicate = ItemNode.parse(duplicateInfo)
|
||||||
|
} else if (node.type === 'column') {
|
||||||
|
duplicate = ColumnNode.parse(duplicateInfo)
|
||||||
} else if (node.type === 'wall') {
|
} else if (node.type === 'wall') {
|
||||||
duplicate = WallNode.parse(duplicateInfo)
|
duplicate = WallNode.parse(duplicateInfo)
|
||||||
} else if (node.type === 'fence') {
|
} else if (node.type === 'fence') {
|
||||||
@@ -322,6 +325,7 @@ export function FloatingActionMenu() {
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
duplicate.type === 'item' ||
|
duplicate.type === 'item' ||
|
||||||
|
duplicate.type === 'column' ||
|
||||||
duplicate.type === 'wall' ||
|
duplicate.type === 'wall' ||
|
||||||
duplicate.type === 'fence' ||
|
duplicate.type === 'fence' ||
|
||||||
duplicate.type === 'window' ||
|
duplicate.type === 'window' ||
|
||||||
@@ -425,7 +429,6 @@ export function FloatingActionMenu() {
|
|||||||
onDuplicate={
|
onDuplicate={
|
||||||
node &&
|
node &&
|
||||||
node.type !== 'spawn' &&
|
node.type !== 'spawn' &&
|
||||||
node.type !== 'column' &&
|
|
||||||
!DELETE_ONLY_TYPES.includes(node.type) &&
|
!DELETE_ONLY_TYPES.includes(node.type) &&
|
||||||
!HOLE_TYPES.includes(node.type)
|
!HOLE_TYPES.includes(node.type)
|
||||||
? handleDuplicate
|
? handleDuplicate
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ function shaftProfileUpdates(shaftProfile: ColumnNode['shaftProfile']): Partial<
|
|||||||
shaftStartScale: 0.72,
|
shaftStartScale: 0.72,
|
||||||
shaftEndScale: 0.72,
|
shaftEndScale: 0.72,
|
||||||
shaftSegmentCount: 1,
|
shaftSegmentCount: 1,
|
||||||
|
shaftTwistStep: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,6 +407,34 @@ export function ColumnPanel() {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<SliderControl
|
||||||
|
label="Segment Twist"
|
||||||
|
max={90}
|
||||||
|
min={-90}
|
||||||
|
onChange={(value) =>
|
||||||
|
handleUpdate({
|
||||||
|
shaftTwistStep: value,
|
||||||
|
...(Math.abs(value) > 0.001 && (node.shaftSegmentCount ?? 1) < 8
|
||||||
|
? { shaftSegmentCount: 12 }
|
||||||
|
: {}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
precision={0}
|
||||||
|
step={5}
|
||||||
|
unit="°"
|
||||||
|
value={node.shaftTwistStep ?? 0}
|
||||||
|
/>
|
||||||
|
{Math.abs(node.shaftTwistStep ?? 0) > 0.001 && (
|
||||||
|
<SliderControl
|
||||||
|
label="Twist Segments"
|
||||||
|
max={48}
|
||||||
|
min={4}
|
||||||
|
onChange={(value) => handleUpdate({ shaftSegmentCount: Math.round(value) })}
|
||||||
|
precision={0}
|
||||||
|
step={1}
|
||||||
|
value={node.shaftSegmentCount ?? 12}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<SliderControl
|
<SliderControl
|
||||||
label="Ring Pairs"
|
label="Ring Pairs"
|
||||||
max={4}
|
max={4}
|
||||||
|
|||||||
@@ -1,18 +1,198 @@
|
|||||||
import { type ColumnNode, useLiveTransforms, useRegistry } from '@pascal-app/core'
|
import { type ColumnNode, useLiveTransforms, useRegistry } from '@pascal-app/core'
|
||||||
import { createContext, useContext, useMemo, useRef } from 'react'
|
import { createContext, useContext, useMemo, useRef } from 'react'
|
||||||
import type { Group, Material } from 'three'
|
import {
|
||||||
|
BoxGeometry,
|
||||||
|
type BufferGeometry,
|
||||||
|
CylinderGeometry,
|
||||||
|
Float32BufferAttribute,
|
||||||
|
type Group,
|
||||||
|
type Material,
|
||||||
|
SphereGeometry,
|
||||||
|
TorusGeometry,
|
||||||
|
} from 'three'
|
||||||
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js'
|
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js'
|
||||||
import { useNodeEvents } from '../../../hooks/use-node-events'
|
import { useNodeEvents } from '../../../hooks/use-node-events'
|
||||||
import { baseMaterial, createMaterial, createMaterialFromPresetRef } from '../../../lib/materials'
|
import { baseMaterial, createMaterial, createMaterialFromPresetRef } from '../../../lib/materials'
|
||||||
|
|
||||||
const ColumnMaterialContext = createContext<Material>(baseMaterial as Material)
|
const ColumnMaterialContext = createContext<Material>(baseMaterial as Material)
|
||||||
const ColumnEdgeSoftnessContext = createContext(0.025)
|
const ColumnEdgeSoftnessContext = createContext(0.025)
|
||||||
|
const COLUMN_UV_SCALE = 1
|
||||||
|
|
||||||
function ColumnMaterial() {
|
function ColumnMaterial() {
|
||||||
const material = useContext(ColumnMaterialContext)
|
const material = useContext(ColumnMaterialContext)
|
||||||
return <primitive attach="material" object={material} />
|
return <primitive attach="material" object={material} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setUvAttributes(geometry: BufferGeometry, uvs: number[]) {
|
||||||
|
geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2))
|
||||||
|
geometry.setAttribute('uv2', new Float32BufferAttribute(uvs.slice(), 2))
|
||||||
|
return geometry
|
||||||
|
}
|
||||||
|
|
||||||
|
function toUvReadyGeometry(geometry: BufferGeometry) {
|
||||||
|
return geometry.index ? geometry.toNonIndexed() : geometry
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPlanarColumnUvs(geometry: BufferGeometry) {
|
||||||
|
const mappedGeometry = toUvReadyGeometry(geometry)
|
||||||
|
const positions = mappedGeometry.getAttribute('position')
|
||||||
|
const normals = mappedGeometry.getAttribute('normal')
|
||||||
|
const uvs: number[] = []
|
||||||
|
|
||||||
|
for (let index = 0; index < positions.count; index += 1) {
|
||||||
|
const x = positions.getX(index)
|
||||||
|
const y = positions.getY(index)
|
||||||
|
const z = positions.getZ(index)
|
||||||
|
const normalX = normals ? Math.abs(normals.getX(index)) : 0
|
||||||
|
const normalY = normals ? Math.abs(normals.getY(index)) : 1
|
||||||
|
const normalZ = normals ? Math.abs(normals.getZ(index)) : 0
|
||||||
|
|
||||||
|
if (normalY >= normalX && normalY >= normalZ) {
|
||||||
|
uvs.push(x * COLUMN_UV_SCALE, z * COLUMN_UV_SCALE)
|
||||||
|
} else if (normalX >= normalZ) {
|
||||||
|
uvs.push(z * COLUMN_UV_SCALE, y * COLUMN_UV_SCALE)
|
||||||
|
} else {
|
||||||
|
uvs.push(x * COLUMN_UV_SCALE, y * COLUMN_UV_SCALE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return setUvAttributes(mappedGeometry, uvs)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ellipseCircumference(radiusX: number, radiusZ: number) {
|
||||||
|
const a = Math.max(0.001, Math.abs(radiusX))
|
||||||
|
const b = Math.max(0.001, Math.abs(radiusZ))
|
||||||
|
return Math.PI * (3 * (a + b) - Math.sqrt((3 * a + b) * (a + 3 * b)))
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCylindricalColumnUvs(
|
||||||
|
geometry: BufferGeometry,
|
||||||
|
sideCircumference: number,
|
||||||
|
height: number,
|
||||||
|
) {
|
||||||
|
const mappedGeometry = toUvReadyGeometry(geometry)
|
||||||
|
const positions = mappedGeometry.getAttribute('position')
|
||||||
|
const normals = mappedGeometry.getAttribute('normal')
|
||||||
|
const defaultUvs = mappedGeometry.getAttribute('uv')
|
||||||
|
const halfHeight = height / 2
|
||||||
|
const uvs: number[] = []
|
||||||
|
|
||||||
|
for (let index = 0; index < positions.count; index += 1) {
|
||||||
|
const x = positions.getX(index)
|
||||||
|
const y = positions.getY(index)
|
||||||
|
const z = positions.getZ(index)
|
||||||
|
const normalY = normals ? Math.abs(normals.getY(index)) : 0
|
||||||
|
|
||||||
|
if (normalY > 0.65) {
|
||||||
|
uvs.push(x * COLUMN_UV_SCALE, z * COLUMN_UV_SCALE)
|
||||||
|
} else {
|
||||||
|
const defaultU = defaultUvs ? defaultUvs.getX(index) : 0
|
||||||
|
uvs.push(defaultU * sideCircumference * COLUMN_UV_SCALE, (y + halfHeight) * COLUMN_UV_SCALE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return setUvAttributes(mappedGeometry, uvs)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySphericalColumnUvs(geometry: BufferGeometry, radius: number) {
|
||||||
|
const mappedGeometry = toUvReadyGeometry(geometry)
|
||||||
|
const defaultUvs = mappedGeometry.getAttribute('uv')
|
||||||
|
if (!defaultUvs) return mappedGeometry
|
||||||
|
|
||||||
|
const uvs: number[] = []
|
||||||
|
const circumference = Math.PI * 2 * radius
|
||||||
|
const arcHeight = Math.PI * radius
|
||||||
|
|
||||||
|
for (let index = 0; index < defaultUvs.count; index += 1) {
|
||||||
|
uvs.push(
|
||||||
|
defaultUvs.getX(index) * circumference * COLUMN_UV_SCALE,
|
||||||
|
defaultUvs.getY(index) * arcHeight * COLUMN_UV_SCALE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return setUvAttributes(mappedGeometry, uvs)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTorusColumnUvs(geometry: BufferGeometry, ringRadius: number, tubeRadius: number) {
|
||||||
|
const mappedGeometry = toUvReadyGeometry(geometry)
|
||||||
|
const defaultUvs = mappedGeometry.getAttribute('uv')
|
||||||
|
if (!defaultUvs) return mappedGeometry
|
||||||
|
|
||||||
|
const uvs: number[] = []
|
||||||
|
const ringLength = Math.PI * 2 * Math.max(0.001, ringRadius)
|
||||||
|
const tubeLength = Math.PI * 2 * Math.max(0.001, tubeRadius)
|
||||||
|
|
||||||
|
for (let index = 0; index < defaultUvs.count; index += 1) {
|
||||||
|
uvs.push(
|
||||||
|
defaultUvs.getX(index) * ringLength * COLUMN_UV_SCALE,
|
||||||
|
defaultUvs.getY(index) * tubeLength * COLUMN_UV_SCALE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return setUvAttributes(mappedGeometry, uvs)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createColumnBoxGeometry(width: number, height: number, depth: number, bevelRadius = 0) {
|
||||||
|
const geometry =
|
||||||
|
bevelRadius > 0.001
|
||||||
|
? new RoundedBoxGeometry(width, height, depth, 3, bevelRadius)
|
||||||
|
: new BoxGeometry(width, height, depth)
|
||||||
|
return applyPlanarColumnUvs(geometry)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createColumnCylinderGeometry({
|
||||||
|
height,
|
||||||
|
radiusBottom,
|
||||||
|
radiusTop = radiusBottom,
|
||||||
|
radiusX = 1,
|
||||||
|
radiusZ = 1,
|
||||||
|
segments = 32,
|
||||||
|
}: {
|
||||||
|
height: number
|
||||||
|
radiusBottom: number
|
||||||
|
radiusTop?: number
|
||||||
|
radiusX?: number
|
||||||
|
radiusZ?: number
|
||||||
|
segments?: number
|
||||||
|
}) {
|
||||||
|
const geometry = new CylinderGeometry(radiusTop, radiusBottom, height, segments)
|
||||||
|
geometry.scale(radiusX, 1, radiusZ)
|
||||||
|
const sideRadius = Math.max(radiusTop, radiusBottom)
|
||||||
|
return applyCylindricalColumnUvs(
|
||||||
|
geometry,
|
||||||
|
ellipseCircumference(sideRadius * radiusX, sideRadius * radiusZ),
|
||||||
|
height,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createColumnSphereGeometry(radius: number, widthSegments = 10, heightSegments = 8) {
|
||||||
|
return applySphericalColumnUvs(new SphereGeometry(radius, widthSegments, heightSegments), radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
function createColumnTorusGeometry({
|
||||||
|
arc = Math.PI * 2,
|
||||||
|
radialSegments = 10,
|
||||||
|
ringRadius,
|
||||||
|
scaleX = ringRadius,
|
||||||
|
scaleY = ringRadius,
|
||||||
|
scaleZ = 1,
|
||||||
|
tubeRadius,
|
||||||
|
tubularSegments = 24,
|
||||||
|
}: {
|
||||||
|
arc?: number
|
||||||
|
radialSegments?: number
|
||||||
|
ringRadius: number
|
||||||
|
scaleX?: number
|
||||||
|
scaleY?: number
|
||||||
|
scaleZ?: number
|
||||||
|
tubeRadius: number
|
||||||
|
tubularSegments?: number
|
||||||
|
}) {
|
||||||
|
const geometry = new TorusGeometry(1, 0.18, radialSegments, tubularSegments, arc)
|
||||||
|
geometry.scale(scaleX, scaleY, scaleZ)
|
||||||
|
return applyTorusColumnUvs(geometry, ringRadius, tubeRadius)
|
||||||
|
}
|
||||||
|
|
||||||
function createColumnMaterial({
|
function createColumnMaterial({
|
||||||
material,
|
material,
|
||||||
materialPreset,
|
materialPreset,
|
||||||
@@ -36,12 +216,19 @@ function getShaftProfile(node: ColumnNode) {
|
|||||||
function getShaftSegmentCount(node: ColumnNode) {
|
function getShaftSegmentCount(node: ColumnNode) {
|
||||||
const shaftProfile = getShaftProfile(node)
|
const shaftProfile = getShaftProfile(node)
|
||||||
const shaftTaper = node.shaftTaper ?? 0
|
const shaftTaper = node.shaftTaper ?? 0
|
||||||
|
const hasTwist = Math.abs(node.shaftTwistStep ?? 0) > 0.001
|
||||||
return Math.max(
|
return Math.max(
|
||||||
1,
|
hasTwist ? 4 : 1,
|
||||||
shaftProfile === 'straight' && shaftTaper <= 0 ? 1 : (node.shaftSegmentCount ?? 24),
|
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) {
|
function getShaftScaleAt(node: ColumnNode, t: number) {
|
||||||
const shaftProfile = getShaftProfile(node)
|
const shaftProfile = getShaftProfile(node)
|
||||||
const shaftTaper = Math.min(node.shaftTaper ?? 0, 0.85)
|
const shaftTaper = Math.min(node.shaftTaper ?? 0, 0.85)
|
||||||
@@ -70,6 +257,181 @@ function getShaftScaleAt(node: ColumnNode, t: number) {
|
|||||||
return Math.max(0.1, profileScale)
|
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 (
|
||||||
|
<mesh dispose={null} position={position} rotation={rotation}>
|
||||||
|
<primitive attach="geometry" dispose={null} object={geometry} />
|
||||||
|
<ColumnMaterial />
|
||||||
|
</mesh>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<mesh dispose={null} position={position} rotation={rotation}>
|
||||||
|
<primitive attach="geometry" dispose={null} object={geometry} />
|
||||||
|
<ColumnMaterial />
|
||||||
|
</mesh>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<mesh dispose={null} position={position} rotation={rotation}>
|
||||||
|
<primitive attach="geometry" dispose={null} object={geometry} />
|
||||||
|
<ColumnMaterial />
|
||||||
|
</mesh>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<mesh dispose={null} position={position}>
|
||||||
|
<primitive attach="geometry" dispose={null} object={geometry} />
|
||||||
|
<ColumnMaterial />
|
||||||
|
</mesh>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<mesh dispose={null} position={position} rotation={rotation}>
|
||||||
|
<primitive attach="geometry" dispose={null} object={geometry} />
|
||||||
|
<ColumnMaterial />
|
||||||
|
</mesh>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function SquareBlock({
|
function SquareBlock({
|
||||||
y,
|
y,
|
||||||
height,
|
height,
|
||||||
@@ -83,31 +445,14 @@ function SquareBlock({
|
|||||||
depth: number
|
depth: number
|
||||||
softenEdges?: boolean
|
softenEdges?: boolean
|
||||||
}) {
|
}) {
|
||||||
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 roundedGeometry = useMemo(() => {
|
|
||||||
if (bevelRadius <= 0.001) return null
|
|
||||||
return new RoundedBoxGeometry(width, height, depth, 3, bevelRadius)
|
|
||||||
}, [bevelRadius, depth, height, width])
|
|
||||||
|
|
||||||
if (height <= 0) return null
|
|
||||||
|
|
||||||
const position = [0, y + height / 2, 0] as const
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh
|
<MappedBox
|
||||||
dispose={null}
|
depth={depth}
|
||||||
position={position}
|
height={height}
|
||||||
scale={roundedGeometry ? undefined : [width, height, depth]}
|
position={[0, y + height / 2, 0]}
|
||||||
>
|
softenEdges={softenEdges}
|
||||||
{roundedGeometry ? (
|
width={width}
|
||||||
<primitive attach="geometry" dispose={null} object={roundedGeometry} />
|
/>
|
||||||
) : (
|
|
||||||
<boxGeometry args={[1, 1, 1]} dispose={null} />
|
|
||||||
)}
|
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,13 +471,13 @@ function RoundBlock({
|
|||||||
radius: number
|
radius: number
|
||||||
segments?: number
|
segments?: number
|
||||||
}) {
|
}) {
|
||||||
if (height <= 0) return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh position={[x, y + height / 2, z]} scale={[radius, height, radius]}>
|
<MappedCylinder
|
||||||
<cylinderGeometry args={[1, 1, 1, segments]} />
|
height={height}
|
||||||
<ColumnMaterial />
|
position={[x, y + height / 2, z]}
|
||||||
</mesh>
|
radius={radius}
|
||||||
|
segments={segments}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,13 +541,15 @@ function OvalBlock({
|
|||||||
depth: number
|
depth: number
|
||||||
segments?: number
|
segments?: number
|
||||||
}) {
|
}) {
|
||||||
if (height <= 0) return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh position={[0, y + height / 2, 0]} scale={[width / 2, height, depth / 2]}>
|
<MappedCylinder
|
||||||
<cylinderGeometry args={[1, 1, 1, segments]} />
|
height={height}
|
||||||
<ColumnMaterial />
|
position={[0, y + height / 2, 0]}
|
||||||
</mesh>
|
radius={1}
|
||||||
|
radiusX={width / 2}
|
||||||
|
radiusZ={depth / 2}
|
||||||
|
segments={segments}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,13 +587,14 @@ function TaperedRoundShaft({ node, y, height }: { node: ColumnNode; y: number; h
|
|||||||
const t = (index + 0.5) / segmentCount
|
const t = (index + 0.5) / segmentCount
|
||||||
const profileScale = getShaftScaleAt(node, t)
|
const profileScale = getShaftScaleAt(node, t)
|
||||||
return (
|
return (
|
||||||
<RoundBlock
|
<group key={index} rotation={[0, getShaftTwistRadians(node, index), 0]}>
|
||||||
height={segmentHeight * 1.015}
|
<RoundBlock
|
||||||
key={index}
|
height={segmentHeight * 1.015}
|
||||||
radius={node.radius * profileScale}
|
radius={node.radius * profileScale}
|
||||||
segments={getSegments(node)}
|
segments={getSegments(node)}
|
||||||
y={y + index * segmentHeight}
|
y={y + index * segmentHeight}
|
||||||
/>
|
/>
|
||||||
|
</group>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</group>
|
</group>
|
||||||
@@ -263,14 +611,15 @@ function TaperedSquareShaft({ node, y, height }: { node: ColumnNode; y: number;
|
|||||||
const t = (index + 0.5) / segmentCount
|
const t = (index + 0.5) / segmentCount
|
||||||
const profileScale = getShaftScaleAt(node, t)
|
const profileScale = getShaftScaleAt(node, t)
|
||||||
return (
|
return (
|
||||||
<RoundedRectangleShaftSegment
|
<group key={index} rotation={[0, getShaftTwistRadians(node, index), 0]}>
|
||||||
cornerRadius={(node.shaftCornerRadius ?? 0.035) * profileScale}
|
<RoundedRectangleShaftSegment
|
||||||
depth={node.depth * profileScale}
|
cornerRadius={(node.shaftCornerRadius ?? 0.035) * profileScale}
|
||||||
height={segmentHeight * 1.015}
|
depth={node.depth * profileScale}
|
||||||
key={index}
|
height={segmentHeight * 1.015}
|
||||||
width={node.width * profileScale}
|
width={node.width * profileScale}
|
||||||
y={y + index * segmentHeight}
|
y={y + index * segmentHeight}
|
||||||
/>
|
/>
|
||||||
|
</group>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</group>
|
</group>
|
||||||
@@ -285,13 +634,7 @@ function Shaft({ node, y, height }: { node: ColumnNode; y: number; height: numbe
|
|||||||
const offset = Math.max(node.radius * 0.78, node.width * 0.22)
|
const offset = Math.max(node.radius * 0.78, node.width * 0.22)
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
<mesh
|
<RoundBlock height={height} radius={node.radius * 0.62} segments={24} y={y} />
|
||||||
position={[0, y + height / 2, 0]}
|
|
||||||
scale={[node.radius * 0.62, height, node.radius * 0.62]}
|
|
||||||
>
|
|
||||||
<cylinderGeometry args={[1, 1, 1, 24]} />
|
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
{(
|
{(
|
||||||
[
|
[
|
||||||
[offset, 0],
|
[offset, 0],
|
||||||
@@ -300,14 +643,15 @@ function Shaft({ node, y, height }: { node: ColumnNode; y: number; height: numbe
|
|||||||
[0, -offset],
|
[0, -offset],
|
||||||
] satisfies [number, number][]
|
] satisfies [number, number][]
|
||||||
).map(([x, z], index) => (
|
).map(([x, z], index) => (
|
||||||
<mesh
|
<RoundBlock
|
||||||
|
height={height}
|
||||||
key={`${x}-${z}-${index}`}
|
key={`${x}-${z}-${index}`}
|
||||||
position={[x, y + height / 2, z]}
|
radius={sideRadius}
|
||||||
scale={[sideRadius, height, sideRadius]}
|
segments={16}
|
||||||
>
|
x={x}
|
||||||
<cylinderGeometry args={[1, 1, 1, 16]} />
|
y={y}
|
||||||
<ColumnMaterial />
|
z={z}
|
||||||
</mesh>
|
/>
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
)
|
)
|
||||||
@@ -439,19 +783,18 @@ function Base({ node, height }: { node: ColumnNode; height: number }) {
|
|||||||
{Array.from({ length: ribCount }, (_, index) => {
|
{Array.from({ length: ribCount }, (_, index) => {
|
||||||
const angle = (index / ribCount) * Math.PI * 2
|
const angle = (index / ribCount) * Math.PI * 2
|
||||||
return (
|
return (
|
||||||
<mesh
|
<MappedCylinder
|
||||||
|
height={height * 0.38}
|
||||||
key={index}
|
key={index}
|
||||||
position={[
|
position={[
|
||||||
Math.cos(angle) * baseRadius * 0.86,
|
Math.cos(angle) * baseRadius * 0.86,
|
||||||
height * 0.58,
|
height * 0.58,
|
||||||
Math.sin(angle) * baseRadius * 0.86,
|
Math.sin(angle) * baseRadius * 0.86,
|
||||||
]}
|
]}
|
||||||
|
radius={ribRadius}
|
||||||
rotation={[0, -angle, 0]}
|
rotation={[0, -angle, 0]}
|
||||||
scale={[ribRadius, height * 0.38, ribRadius]}
|
segments={6}
|
||||||
>
|
/>
|
||||||
<cylinderGeometry args={[1, 1, 1, 6]} />
|
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<RoundBlock
|
<RoundBlock
|
||||||
@@ -482,15 +825,15 @@ function Base({ node, height }: { node: ColumnNode; height: number }) {
|
|||||||
[-node.width * widthScale * 0.51, 0, Math.PI / 2],
|
[-node.width * widthScale * 0.51, 0, Math.PI / 2],
|
||||||
] satisfies [number, number, number][]
|
] satisfies [number, number, number][]
|
||||||
).map(([x, z, rotation], index) => (
|
).map(([x, z, rotation], index) => (
|
||||||
<mesh
|
<MappedBox
|
||||||
|
depth={inset}
|
||||||
|
height={height * 0.42}
|
||||||
key={index}
|
key={index}
|
||||||
position={[x, height * 0.5, z]}
|
position={[x, height * 0.5, z]}
|
||||||
rotation={[0, rotation, 0]}
|
rotation={[0, rotation, 0]}
|
||||||
scale={[node.width * 0.36, height * 0.42, inset]}
|
softenEdges={false}
|
||||||
>
|
width={node.width * 0.36}
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
/>
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
)
|
)
|
||||||
@@ -515,15 +858,15 @@ function BaseCarvings({ node, height }: { node: ColumnNode; height: number }) {
|
|||||||
{Array.from({ length: count }, (_, index) => {
|
{Array.from({ length: count }, (_, index) => {
|
||||||
const angle = (index / count) * Math.PI * 2
|
const angle = (index / count) * Math.PI * 2
|
||||||
return (
|
return (
|
||||||
<mesh
|
<MappedCone
|
||||||
|
height={height * 0.28}
|
||||||
key={index}
|
key={index}
|
||||||
position={[Math.cos(angle) * radius, y, Math.sin(angle) * radius]}
|
position={[Math.cos(angle) * radius, y, Math.sin(angle) * radius]}
|
||||||
|
radiusX={0.014}
|
||||||
|
radiusZ={0.01}
|
||||||
rotation={[0.36, -angle, 0]}
|
rotation={[0.36, -angle, 0]}
|
||||||
scale={[0.014, height * 0.28, 0.01]}
|
segments={5}
|
||||||
>
|
/>
|
||||||
<coneGeometry args={[1, 1, 5]} />
|
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</group>
|
</group>
|
||||||
@@ -653,14 +996,13 @@ function Flutes({
|
|||||||
const x = Math.cos(angle) * shaftRadius
|
const x = Math.cos(angle) * shaftRadius
|
||||||
const z = Math.sin(angle) * shaftRadius
|
const z = Math.sin(angle) * shaftRadius
|
||||||
return (
|
return (
|
||||||
<mesh
|
<MappedCylinder
|
||||||
|
height={shaftHeight * 0.92}
|
||||||
key={index}
|
key={index}
|
||||||
position={[x, shaftY + shaftHeight / 2, z]}
|
position={[x, shaftY + shaftHeight / 2, z]}
|
||||||
scale={[fluteRadius, shaftHeight * 0.92, fluteRadius]}
|
radius={fluteRadius}
|
||||||
>
|
segments={8}
|
||||||
<cylinderGeometry args={[1, 1, 1, 8]} />
|
/>
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</group>
|
</group>
|
||||||
@@ -699,33 +1041,54 @@ function DravidianShaftPanels({
|
|||||||
rotation?: number
|
rotation?: number
|
||||||
}) => (
|
}) => (
|
||||||
<group position={position} rotation={[0, rotation, 0]}>
|
<group position={position} rotation={[0, rotation, 0]}>
|
||||||
<mesh position={[0, panelHeight / 2, 0]} scale={[panelWidth, rail, reliefDepth]}>
|
<MappedBox
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
depth={reliefDepth}
|
||||||
<ColumnMaterial />
|
height={rail}
|
||||||
</mesh>
|
position={[0, panelHeight / 2, 0]}
|
||||||
<mesh position={[0, -panelHeight / 2, 0]} scale={[panelWidth, rail, reliefDepth]}>
|
softenEdges={false}
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
width={panelWidth}
|
||||||
<ColumnMaterial />
|
/>
|
||||||
</mesh>
|
<MappedBox
|
||||||
<mesh position={[panelWidth / 2, 0, 0]} scale={[rail, panelHeight, reliefDepth]}>
|
depth={reliefDepth}
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
height={rail}
|
||||||
<ColumnMaterial />
|
position={[0, -panelHeight / 2, 0]}
|
||||||
</mesh>
|
softenEdges={false}
|
||||||
<mesh position={[-panelWidth / 2, 0, 0]} scale={[rail, panelHeight, reliefDepth]}>
|
width={panelWidth}
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
/>
|
||||||
<ColumnMaterial />
|
<MappedBox
|
||||||
</mesh>
|
depth={reliefDepth}
|
||||||
|
height={panelHeight}
|
||||||
|
position={[panelWidth / 2, 0, 0]}
|
||||||
|
softenEdges={false}
|
||||||
|
width={rail}
|
||||||
|
/>
|
||||||
|
<MappedBox
|
||||||
|
depth={reliefDepth}
|
||||||
|
height={panelHeight}
|
||||||
|
position={[-panelWidth / 2, 0, 0]}
|
||||||
|
softenEdges={false}
|
||||||
|
width={rail}
|
||||||
|
/>
|
||||||
{panelShape === 'diamond' && (
|
{panelShape === 'diamond' && (
|
||||||
<mesh rotation={[0, 0, Math.PI / 4]} scale={[rail * 1.2, panelHeight * 0.42, reliefDepth]}>
|
<MappedBox
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
depth={reliefDepth}
|
||||||
<ColumnMaterial />
|
height={panelHeight * 0.42}
|
||||||
</mesh>
|
position={[0, 0, 0]}
|
||||||
|
rotation={[0, 0, Math.PI / 4]}
|
||||||
|
softenEdges={false}
|
||||||
|
width={rail * 1.2}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{panelShape === 'arched' && (
|
{panelShape === 'arched' && (
|
||||||
<mesh position={[0, panelHeight * 0.28, 0]} scale={[panelWidth * 0.42, rail, reliefDepth]}>
|
<MappedTorus
|
||||||
<torusGeometry args={[1, 0.18, 8, 16, Math.PI]} />
|
arc={Math.PI}
|
||||||
<ColumnMaterial />
|
position={[0, panelHeight * 0.28, 0]}
|
||||||
</mesh>
|
ringRadius={panelWidth * 0.42}
|
||||||
|
scaleX={panelWidth * 0.42}
|
||||||
|
scaleY={rail}
|
||||||
|
scaleZ={reliefDepth}
|
||||||
|
tubeRadius={Math.max(rail, reliefDepth) * 0.18}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</group>
|
</group>
|
||||||
)
|
)
|
||||||
@@ -781,19 +1144,18 @@ function SpiralRibs({
|
|||||||
const angle = (ribIndex / ribCount) * Math.PI * 2 + t * spiralTwist * Math.PI * 2
|
const angle = (ribIndex / ribCount) * Math.PI * 2 + t * spiralTwist * Math.PI * 2
|
||||||
const taperScale = 1 - Math.min(shaftTaper, 0.85) * t
|
const taperScale = 1 - Math.min(shaftTaper, 0.85) * t
|
||||||
return (
|
return (
|
||||||
<mesh
|
<MappedCylinder
|
||||||
|
height={segmentHeight}
|
||||||
key={index}
|
key={index}
|
||||||
position={[
|
position={[
|
||||||
Math.cos(angle) * ribDistance * taperScale,
|
Math.cos(angle) * ribDistance * taperScale,
|
||||||
shaftY + shaftHeight * t,
|
shaftY + shaftHeight * t,
|
||||||
Math.sin(angle) * ribDistance * taperScale,
|
Math.sin(angle) * ribDistance * taperScale,
|
||||||
]}
|
]}
|
||||||
|
radius={ribWidth}
|
||||||
rotation={[0, -angle, lean]}
|
rotation={[0, -angle, lean]}
|
||||||
scale={[ribWidth, segmentHeight, ribWidth]}
|
segments={8}
|
||||||
>
|
/>
|
||||||
<cylinderGeometry args={[1, 1, 1, 8]} />
|
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</group>
|
</group>
|
||||||
@@ -830,19 +1192,18 @@ function LowerCarvedBand({
|
|||||||
{Array.from({ length: count }, (_, index) => {
|
{Array.from({ length: count }, (_, index) => {
|
||||||
const angle = (index / count) * Math.PI * 2
|
const angle = (index / count) * Math.PI * 2
|
||||||
return (
|
return (
|
||||||
<mesh
|
<MappedCylinder
|
||||||
|
height={bandHeight * 0.62}
|
||||||
key={index}
|
key={index}
|
||||||
position={[
|
position={[
|
||||||
Math.cos(angle) * distance,
|
Math.cos(angle) * distance,
|
||||||
y + bandHeight * 0.5,
|
y + bandHeight * 0.5,
|
||||||
Math.sin(angle) * distance,
|
Math.sin(angle) * distance,
|
||||||
]}
|
]}
|
||||||
|
radius={0.012}
|
||||||
rotation={[0, -angle, 0]}
|
rotation={[0, -angle, 0]}
|
||||||
scale={[0.012, bandHeight * 0.62, 0.012]}
|
segments={5}
|
||||||
>
|
/>
|
||||||
<cylinderGeometry args={[1, 1, 1, 5]} />
|
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</group>
|
</group>
|
||||||
@@ -907,26 +1268,38 @@ function CapitalCarvings({
|
|||||||
))}
|
))}
|
||||||
{xPositions.map((x, index) => (
|
{xPositions.map((x, index) => (
|
||||||
<group key={`front-back-dentil-${index}`}>
|
<group key={`front-back-dentil-${index}`}>
|
||||||
<mesh position={[x, y, halfDepth]} scale={[dentilWidth, dentilHeight, dentilDepth]}>
|
<MappedBox
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
depth={dentilDepth}
|
||||||
<ColumnMaterial />
|
height={dentilHeight}
|
||||||
</mesh>
|
position={[x, y, halfDepth]}
|
||||||
<mesh position={[x, y, -halfDepth]} scale={[dentilWidth, dentilHeight, dentilDepth]}>
|
softenEdges={false}
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
width={dentilWidth}
|
||||||
<ColumnMaterial />
|
/>
|
||||||
</mesh>
|
<MappedBox
|
||||||
|
depth={dentilDepth}
|
||||||
|
height={dentilHeight}
|
||||||
|
position={[x, y, -halfDepth]}
|
||||||
|
softenEdges={false}
|
||||||
|
width={dentilWidth}
|
||||||
|
/>
|
||||||
</group>
|
</group>
|
||||||
))}
|
))}
|
||||||
{zPositions.map((z, index) => (
|
{zPositions.map((z, index) => (
|
||||||
<group key={`side-dentil-${index}`}>
|
<group key={`side-dentil-${index}`}>
|
||||||
<mesh position={[halfWidth, y, z]} scale={[dentilDepth, dentilHeight, dentilWidth]}>
|
<MappedBox
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
depth={dentilWidth}
|
||||||
<ColumnMaterial />
|
height={dentilHeight}
|
||||||
</mesh>
|
position={[halfWidth, y, z]}
|
||||||
<mesh position={[-halfWidth, y, z]} scale={[dentilDepth, dentilHeight, dentilWidth]}>
|
softenEdges={false}
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
width={dentilDepth}
|
||||||
<ColumnMaterial />
|
/>
|
||||||
</mesh>
|
<MappedBox
|
||||||
|
depth={dentilWidth}
|
||||||
|
height={dentilHeight}
|
||||||
|
position={[-halfWidth, y, z]}
|
||||||
|
softenEdges={false}
|
||||||
|
width={dentilDepth}
|
||||||
|
/>
|
||||||
</group>
|
</group>
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
@@ -946,14 +1319,11 @@ function CapitalCarvings({
|
|||||||
{Array.from({ length: beadCount }, (_, index) => {
|
{Array.from({ length: beadCount }, (_, index) => {
|
||||||
const angle = (index / beadCount) * Math.PI * 2
|
const angle = (index / beadCount) * Math.PI * 2
|
||||||
return (
|
return (
|
||||||
<mesh
|
<MappedSphere
|
||||||
key={`bead-${index}`}
|
key={`bead-${index}`}
|
||||||
position={[Math.cos(angle) * beadDistance, beadY, Math.sin(angle) * beadDistance]}
|
position={[Math.cos(angle) * beadDistance, beadY, Math.sin(angle) * beadDistance]}
|
||||||
scale={[beadRadius, beadRadius, beadRadius]}
|
radius={beadRadius}
|
||||||
>
|
/>
|
||||||
<sphereGeometry args={[1, 10, 8]} />
|
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</group>
|
</group>
|
||||||
@@ -1015,15 +1385,14 @@ function Volutes({
|
|||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
{volutes.map((volute, index) => (
|
{volutes.map((volute, index) => (
|
||||||
<mesh
|
<MappedTorus
|
||||||
key={index}
|
key={index}
|
||||||
position={volute.position}
|
position={volute.position}
|
||||||
|
ringRadius={radius}
|
||||||
rotation={volute.rotation}
|
rotation={volute.rotation}
|
||||||
scale={[radius, radius, radius * 0.28]}
|
scaleZ={radius * 0.28}
|
||||||
>
|
tubeRadius={radius * 0.18}
|
||||||
<torusGeometry args={[1, 0.18, 10, 24]} />
|
/>
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
)
|
)
|
||||||
@@ -1060,15 +1429,15 @@ function LeafCarvings({
|
|||||||
Array.from({ length: leafCount }, (_, index) => {
|
Array.from({ length: leafCount }, (_, index) => {
|
||||||
const angle = (index / leafCount) * Math.PI * 2 + row.offset
|
const angle = (index / leafCount) * Math.PI * 2 + row.offset
|
||||||
return (
|
return (
|
||||||
<mesh
|
<MappedCone
|
||||||
|
height={capitalHeight * row.scale}
|
||||||
key={`${rowIndex}-${index}`}
|
key={`${rowIndex}-${index}`}
|
||||||
position={[Math.cos(angle) * distance, row.y, Math.sin(angle) * distance]}
|
position={[Math.cos(angle) * distance, row.y, Math.sin(angle) * distance]}
|
||||||
|
radiusX={0.018}
|
||||||
|
radiusZ={0.01}
|
||||||
rotation={[0.48, -angle, 0]}
|
rotation={[0.48, -angle, 0]}
|
||||||
scale={[0.018, capitalHeight * row.scale, 0.01]}
|
segments={6}
|
||||||
>
|
/>
|
||||||
<coneGeometry args={[1, 1, 6]} />
|
|
||||||
<ColumnMaterial />
|
|
||||||
</mesh>
|
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)}
|
)}
|
||||||
@@ -1106,14 +1475,14 @@ function Capital({ node, y, height }: { node: ColumnNode; y: number; height: num
|
|||||||
const angle = (index / count) * Math.PI * 2
|
const angle = (index / count) * Math.PI * 2
|
||||||
const distance = Math.max(node.width, node.depth) * 0.56
|
const distance = Math.max(node.width, node.depth) * 0.56
|
||||||
return (
|
return (
|
||||||
<mesh
|
<MappedCone
|
||||||
|
height={height * 0.28}
|
||||||
key={index}
|
key={index}
|
||||||
position={[Math.cos(angle) * distance, y - height * 0.1, Math.sin(angle) * distance]}
|
position={[Math.cos(angle) * distance, y - height * 0.1, Math.sin(angle) * distance]}
|
||||||
scale={[0.035, height * 0.28, 0.035]}
|
radiusX={0.035}
|
||||||
>
|
rotation={[0, 0, 0]}
|
||||||
<coneGeometry args={[1, 1, 6]} />
|
segments={6}
|
||||||
<ColumnMaterial />
|
/>
|
||||||
</mesh>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</group>
|
</group>
|
||||||
|
|||||||
Reference in New Issue
Block a user