Refactor guide events and column placement handling
This commit is contained in:
@@ -7,7 +7,6 @@ import type {
|
||||
ColumnNode,
|
||||
DoorNode,
|
||||
FenceNode,
|
||||
GuideNode,
|
||||
ItemNode,
|
||||
LevelNode,
|
||||
RoofNode,
|
||||
@@ -133,12 +132,6 @@ type ToolEvents = {
|
||||
'tool:cancel': undefined
|
||||
}
|
||||
|
||||
type GuideEvents = {
|
||||
'guide:set-reference-scale': { guideId: GuideNode['id'] }
|
||||
'guide:cancel-reference-scale': undefined
|
||||
'guide:deleted': { guideId: GuideNode['id'] }
|
||||
}
|
||||
|
||||
type PresetEvents = {
|
||||
'preset:generate-thumbnail': { presetId: string; nodeId: string }
|
||||
'preset:thumbnail-updated': { presetId: string; thumbnailUrl: string }
|
||||
@@ -180,7 +173,6 @@ type EditorEvents = GridEvents &
|
||||
NodeEvents<'door', DoorEvent> &
|
||||
CameraControlEvents &
|
||||
ToolEvents &
|
||||
GuideEvents &
|
||||
PresetEvents &
|
||||
ThumbnailEvents &
|
||||
SnapshotEvents &
|
||||
|
||||
@@ -64,6 +64,7 @@ import {
|
||||
rotatePlanVector as rotateSharedPlanVector,
|
||||
type FloorplanNodeTransform as SharedFloorplanNodeTransform,
|
||||
} from '../../lib/floorplan'
|
||||
import { guideEmitter } from '../../lib/guide-events'
|
||||
import { duplicateRoofSubtree } from '../../lib/roof-duplication'
|
||||
import { sfxEmitter } from '../../lib/sfx-bus'
|
||||
import { duplicateStairSubtree } from '../../lib/stair-duplication'
|
||||
@@ -9131,9 +9132,9 @@ export function FloorplanPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
emitter.on('guide:set-reference-scale', handleSetReferenceScale)
|
||||
guideEmitter.on('guide:set-reference-scale', handleSetReferenceScale)
|
||||
return () => {
|
||||
emitter.off('guide:set-reference-scale', handleSetReferenceScale)
|
||||
guideEmitter.off('guide:set-reference-scale', handleSetReferenceScale)
|
||||
}
|
||||
}, [startReferenceScaleForGuide])
|
||||
|
||||
@@ -9143,9 +9144,9 @@ export function FloorplanPanel() {
|
||||
setPendingReferenceScale(null)
|
||||
}
|
||||
|
||||
emitter.on('guide:cancel-reference-scale', handleCancel)
|
||||
guideEmitter.on('guide:cancel-reference-scale', handleCancel)
|
||||
return () => {
|
||||
emitter.off('guide:cancel-reference-scale', handleCancel)
|
||||
guideEmitter.off('guide:cancel-reference-scale', handleCancel)
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -9160,9 +9161,9 @@ export function FloorplanPanel() {
|
||||
clearGuideUi(payload.guideId)
|
||||
}
|
||||
|
||||
emitter.on('guide:deleted', handleDeleted)
|
||||
guideEmitter.on('guide:deleted', handleDeleted)
|
||||
return () => {
|
||||
emitter.off('guide:deleted', handleDeleted)
|
||||
guideEmitter.off('guide:deleted', handleDeleted)
|
||||
}
|
||||
}, [clearGuideUi])
|
||||
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import '../../../three-types'
|
||||
|
||||
import {
|
||||
type AnyNode,
|
||||
COLUMN_PRESETS,
|
||||
ColumnNode,
|
||||
type ColumnNode as ColumnNodeType,
|
||||
type ColumnPresetId,
|
||||
emitter,
|
||||
type GridEvent,
|
||||
type LevelNode,
|
||||
useScene,
|
||||
} from '@pascal-app/core'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import type { Group } from 'three'
|
||||
import { sfxEmitter } from '../../../lib/sfx-bus'
|
||||
@@ -41,9 +40,10 @@ function createColumnFromPreset(presetId: ColumnPresetId, position: [number, num
|
||||
|
||||
type ColumnToolProps = {
|
||||
currentLevelId: LevelNode['id'] | null
|
||||
onPlaced?: (nodeId: ColumnNodeType['id']) => void
|
||||
}
|
||||
|
||||
export const ColumnTool: React.FC<ColumnToolProps> = ({ currentLevelId }) => {
|
||||
export const ColumnTool: React.FC<ColumnToolProps> = ({ currentLevelId, onPlaced }) => {
|
||||
const [, setCursorPosition] = useState<[number, number, number] | null>(null)
|
||||
const cursorRef = useRef<Group>(null)
|
||||
|
||||
@@ -68,7 +68,7 @@ export const ColumnTool: React.FC<ColumnToolProps> = ({ currentLevelId }) => {
|
||||
]
|
||||
const column = createColumnFromPreset(DEFAULT_COLUMN_PRESET_ID, position)
|
||||
useScene.getState().createNode(column, currentLevelId)
|
||||
useViewer.getState().setSelection({ selectedIds: [column.id as AnyNode['id']] })
|
||||
onPlaced?.(column.id)
|
||||
sfxEmitter.emit('sfx:structure-build')
|
||||
useEditor.getState().setTool(null)
|
||||
useEditor.getState().setMode('select')
|
||||
@@ -81,7 +81,7 @@ export const ColumnTool: React.FC<ColumnToolProps> = ({ currentLevelId }) => {
|
||||
emitter.off('grid:move', onGridMove)
|
||||
emitter.off('grid:click', onGridClick)
|
||||
}
|
||||
}, [currentLevelId])
|
||||
}, [currentLevelId, onPlaced])
|
||||
|
||||
if (!currentLevelId) return null
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ export const ToolManager: React.FC = () => {
|
||||
const showBuildTool = mode === 'build' && tool !== null
|
||||
|
||||
const BuildToolComponent = showBuildTool ? tools[phase]?.[tool] : null
|
||||
const handleSpawnSelected = (nodeId: `spawn_${string}`) => {
|
||||
const handlePlacedNodeSelected = (nodeId: AnyNodeId) => {
|
||||
setSelection({ selectedIds: [nodeId] })
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export const ToolManager: React.FC = () => {
|
||||
<>
|
||||
{showSiteBoundaryEditor && <SiteBoundaryEditor />}
|
||||
{/* World-space tools: site boundary and building movement operate in world coordinates */}
|
||||
{movingNode?.type === 'building' && <MoveTool onSpawnMoved={handleSpawnSelected} />}
|
||||
{movingNode?.type === 'building' && <MoveTool onSpawnMoved={handlePlacedNodeSelected} />}
|
||||
|
||||
{/* Building-local group: all other tools are relative to the selected building.
|
||||
Cursor visuals set positions in building-local space; this group applies the
|
||||
@@ -160,13 +160,13 @@ export const ToolManager: React.FC = () => {
|
||||
{curvingWall && <CurveWallTool node={curvingWall} />}
|
||||
{curvingFence && <CurveFenceTool node={curvingFence} />}
|
||||
{movingNode && movingNode.type !== 'building' && (
|
||||
<MoveTool onSpawnMoved={handleSpawnSelected} />
|
||||
<MoveTool onSpawnMoved={handlePlacedNodeSelected} />
|
||||
)}
|
||||
{!movingNode && showBuildTool && tool === 'spawn' && (
|
||||
<SpawnTool currentLevelId={selectedLevelId} onPlaced={handleSpawnSelected} />
|
||||
<SpawnTool currentLevelId={selectedLevelId} onPlaced={handlePlacedNodeSelected} />
|
||||
)}
|
||||
{!movingNode && showBuildTool && tool === 'column' && (
|
||||
<ColumnTool currentLevelId={selectedLevelId} />
|
||||
<ColumnTool currentLevelId={selectedLevelId} onPlaced={handlePlacedNodeSelected} />
|
||||
)}
|
||||
{!movingNode && BuildToolComponent && tool !== 'column' && <BuildToolComponent />}
|
||||
</group>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import {
|
||||
type AnyNode,
|
||||
emitter,
|
||||
type GuideNode,
|
||||
loadAssetUrl,
|
||||
saveAsset,
|
||||
@@ -11,6 +10,7 @@ import {
|
||||
} from '@pascal-app/core'
|
||||
import { Eye, EyeOff, LocateFixed, Lock, RotateCcw, Ruler, Trash2, Unlock, Upload } from 'lucide-react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { guideEmitter } from '../../../lib/guide-events'
|
||||
import { getGuideImageName } from '../../../lib/local-guide-image'
|
||||
import useEditor from '../../../store/use-editor'
|
||||
import { ActionButton, ActionGroup } from '../controls/action-button'
|
||||
@@ -98,7 +98,7 @@ export function ReferencePanel() {
|
||||
}
|
||||
|
||||
deleteNode(selectedReferenceId as AnyNode['id'])
|
||||
emitter.emit('guide:deleted', { guideId: selectedReferenceId as GuideNode['id'] })
|
||||
guideEmitter.emit('guide:deleted', { guideId: selectedReferenceId as GuideNode['id'] })
|
||||
clearGuideUi(selectedReferenceId)
|
||||
setSelectedReferenceId(null)
|
||||
}, [clearGuideUi, deleteNode, node?.type, selectedReferenceId, setSelectedReferenceId])
|
||||
@@ -108,11 +108,11 @@ export function ReferencePanel() {
|
||||
return
|
||||
}
|
||||
|
||||
emitter.emit('guide:set-reference-scale', { guideId: node.id })
|
||||
guideEmitter.emit('guide:set-reference-scale', { guideId: node.id })
|
||||
}, [node])
|
||||
|
||||
const handleCancelScale = useCallback(() => {
|
||||
emitter.emit('guide:cancel-reference-scale')
|
||||
guideEmitter.emit('guide:cancel-reference-scale')
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { GuideNode } from '@pascal-app/core'
|
||||
import mitt from 'mitt'
|
||||
|
||||
type GuideEditorEvents = {
|
||||
'guide:set-reference-scale': { guideId: GuideNode['id'] }
|
||||
'guide:cancel-reference-scale': undefined
|
||||
'guide:deleted': { guideId: GuideNode['id'] }
|
||||
}
|
||||
|
||||
export const guideEmitter = mitt<GuideEditorEvents>()
|
||||
@@ -1,198 +1,23 @@
|
||||
import { type ColumnNode, useLiveTransforms, useRegistry } from '@pascal-app/core'
|
||||
import { createContext, useContext, useMemo, useRef } from 'react'
|
||||
import {
|
||||
BoxGeometry,
|
||||
type BufferGeometry,
|
||||
CylinderGeometry,
|
||||
Float32BufferAttribute,
|
||||
type Group,
|
||||
type Material,
|
||||
SphereGeometry,
|
||||
TorusGeometry,
|
||||
} from 'three'
|
||||
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js'
|
||||
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<Material>(baseMaterial as Material)
|
||||
const ColumnEdgeSoftnessContext = createContext(0.025)
|
||||
const COLUMN_UV_SCALE = 1
|
||||
|
||||
function ColumnMaterial() {
|
||||
const material = useContext(ColumnMaterialContext)
|
||||
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({
|
||||
material,
|
||||
materialPreset,
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
import {
|
||||
BoxGeometry,
|
||||
type BufferGeometry,
|
||||
CylinderGeometry,
|
||||
Float32BufferAttribute,
|
||||
SphereGeometry,
|
||||
TorusGeometry,
|
||||
} from 'three'
|
||||
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js'
|
||||
|
||||
const COLUMN_UV_SCALE = 1
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
export 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)
|
||||
}
|
||||
|
||||
export 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,
|
||||
)
|
||||
}
|
||||
|
||||
export function createColumnSphereGeometry(radius: number, widthSegments = 10, heightSegments = 8) {
|
||||
return applySphericalColumnUvs(new SphereGeometry(radius, widthSegments, heightSegments), radius)
|
||||
}
|
||||
|
||||
export 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)
|
||||
}
|
||||
Reference in New Issue
Block a user