75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
'use client'
|
|
|
|
import { type AnyNode, type AnyNodeId, useScene } from '@pascal-app/core'
|
|
import { triggerSFX } from '@pascal-app/editor'
|
|
import { useViewer } from '@pascal-app/viewer'
|
|
import { useMemo } from 'react'
|
|
import { usePlacement } from './placement'
|
|
import TreePreview from './preview'
|
|
import { TreeNode } from './schema'
|
|
import { useTreesStore } from './store'
|
|
|
|
/**
|
|
* The trees placement tool. Mounted by the host's registry-first `ToolManager`
|
|
* whenever `tool === 'trees:tree'` — no host edit per kind. Reads the panel
|
|
* brush from the plugin store, ghosts a preview at the snapped cursor, and
|
|
* commits a tree on click. Snapping + level conversion live in `usePlacement`.
|
|
*/
|
|
export default function TreeTool() {
|
|
const activeLevelId = useViewer((s) => s.selection.levelId)
|
|
const brush = useTreesStore()
|
|
|
|
const previewNode = useMemo(
|
|
() =>
|
|
TreeNode.parse({
|
|
preset: brush.preset,
|
|
size: brush.size,
|
|
height: brush.height,
|
|
foliageDensity: brush.foliageDensity,
|
|
trunkThickness: brush.trunkThickness,
|
|
leafless: brush.leafless,
|
|
// seed/treeType left unset → the ghost shows the pure preset, as placed.
|
|
position: [0, 0, 0],
|
|
rotation: [0, 0, 0],
|
|
}),
|
|
[
|
|
brush.preset,
|
|
brush.size,
|
|
brush.height,
|
|
brush.foliageDensity,
|
|
brush.trunkThickness,
|
|
brush.leafless,
|
|
],
|
|
)
|
|
|
|
const { cursorRef, cursorVisible } = usePlacement(activeLevelId, (position) => {
|
|
if (!activeLevelId) return
|
|
const s = useTreesStore.getState()
|
|
const tree = TreeNode.parse({
|
|
preset: s.preset,
|
|
size: s.size,
|
|
height: s.height,
|
|
foliageDensity: s.foliageDensity,
|
|
trunkThickness: s.trunkThickness,
|
|
leafless: s.leafless,
|
|
// seed/treeType unset → the pure ez-tree preset (its canonical seed + type).
|
|
// All same-preset trees then share one instancing variant; a random Y
|
|
// rotation keeps a planted row from looking cloned. Use Randomize (inspector)
|
|
// to vary a tree's seed.
|
|
position,
|
|
rotation: [0, (Math.floor(Math.random() * 8) * Math.PI) / 4, 0],
|
|
})
|
|
useScene.getState().createNode(tree as unknown as AnyNode, activeLevelId as AnyNodeId)
|
|
useViewer.getState().setSelection({ selectedIds: [tree.id as AnyNodeId] })
|
|
triggerSFX('sfx:item-place')
|
|
})
|
|
|
|
if (!activeLevelId) return null
|
|
|
|
return (
|
|
<group ref={cursorRef} visible={cursorVisible}>
|
|
<TreePreview node={previewNode} />
|
|
</group>
|
|
)
|
|
}
|