126 lines
4.2 KiB
TypeScript
126 lines
4.2 KiB
TypeScript
import type { FloorplanGeometry, GeometryContext } from '@pascal-app/core'
|
|
import { flowerPetalColor } from './flower-geometry'
|
|
import { FLOWER_PRESETS } from './flower-presets'
|
|
import type { FlowerNode } from './flower-schema'
|
|
import { GRASS_PRESETS } from './grass-presets'
|
|
import type { GrassNode } from './grass-schema'
|
|
import { TREE_PRESETS } from './presets'
|
|
import type { TreeNode } from './schema'
|
|
|
|
/**
|
|
* 2D plan builders for the plant kinds (`def.floorplan`) — the registry
|
|
* floor-plan layer renders any kind that provides one, so this is all it takes
|
|
* for plugin nodes to appear in the 2D view. Classic architect symbols:
|
|
* a dashed canopy circle (dashed = overhead element, like a roof overhang)
|
|
* with a solid trunk dot for trees; small colour dots for flowers/grass.
|
|
*/
|
|
|
|
/** Trunk radius in plan — also the selection footprint, so the move box hugs
|
|
* the trunk instead of the whole canopy. */
|
|
export function treeTrunkRadius(tree: TreeNode): number {
|
|
return Math.max(0.15, (tree.height ?? 7) * 0.025 * (tree.trunkThickness ?? 1))
|
|
}
|
|
|
|
/** Approximate canopy radius in plan (matches the old whole-tree footprint). */
|
|
export function treeCanopyRadius(tree: TreeNode): number {
|
|
return Math.max(0.5, (tree.height ?? 7) * 0.28)
|
|
}
|
|
|
|
type ViewChrome = { stroke: string | null; selected: boolean }
|
|
|
|
/** Selection/hover stroke override shared by the three builders. */
|
|
function chromeOf(ctx: GeometryContext): ViewChrome {
|
|
const view = ctx.viewState
|
|
const palette = view?.palette
|
|
if ((view?.selected || view?.highlighted) && palette)
|
|
return { stroke: palette.selectedStroke, selected: view?.selected ?? false }
|
|
if (view?.hovered && palette) return { stroke: palette.wallHoverStroke, selected: false }
|
|
return { stroke: null, selected: false }
|
|
}
|
|
|
|
export function buildTreeFloorplan(node: TreeNode, ctx: GeometryContext): FloorplanGeometry {
|
|
const [x, , z] = node.position ?? [0, 0, 0]
|
|
const swatch = (TREE_PRESETS[node.preset] ?? TREE_PRESETS.oak).swatch
|
|
const chrome = chromeOf(ctx)
|
|
const stroke = chrome.stroke ?? swatch
|
|
|
|
const children: FloorplanGeometry[] = [
|
|
// Canopy ring — pointer-events on the stroke only, so the (large) disc
|
|
// doesn't steal clicks from whatever sits under the canopy in plan.
|
|
{
|
|
kind: 'circle',
|
|
cx: x,
|
|
cy: z,
|
|
r: treeCanopyRadius(node),
|
|
stroke,
|
|
strokeWidth: 0.03,
|
|
strokeDasharray: '0.18 0.12',
|
|
fill: swatch,
|
|
fillOpacity: 0.06,
|
|
pointerEvents: 'stroke',
|
|
},
|
|
// Trunk dot — the solid, always-clickable core of the symbol.
|
|
{
|
|
kind: 'circle',
|
|
cx: x,
|
|
cy: z,
|
|
r: treeTrunkRadius(node),
|
|
fill: chrome.stroke ?? '#6b4f2e',
|
|
stroke,
|
|
strokeWidth: 0.02,
|
|
opacity: 0.95,
|
|
},
|
|
]
|
|
if (chrome.selected) children.push({ kind: 'move-handle', point: [x, z] })
|
|
return { kind: 'group', children }
|
|
}
|
|
|
|
export function buildFlowerFloorplan(node: FlowerNode, ctx: GeometryContext): FloorplanGeometry {
|
|
const [x, , z] = node.position ?? [0, 0, 0]
|
|
const preset = FLOWER_PRESETS[node.preset] ?? FLOWER_PRESETS.daisy
|
|
const chrome = chromeOf(ctx)
|
|
const children: FloorplanGeometry[] = [
|
|
{
|
|
kind: 'circle',
|
|
cx: x,
|
|
cy: z,
|
|
r: 0.1,
|
|
fill: flowerPetalColor(node),
|
|
stroke: chrome.stroke ?? preset.stemColor,
|
|
strokeWidth: 0.02,
|
|
},
|
|
{
|
|
kind: 'circle',
|
|
cx: x,
|
|
cy: z,
|
|
r: 0.035,
|
|
fill: preset.centerColor,
|
|
pointerEvents: 'none',
|
|
},
|
|
]
|
|
if (chrome.selected) children.push({ kind: 'move-handle', point: [x, z] })
|
|
return { kind: 'group', children }
|
|
}
|
|
|
|
export function buildGrassFloorplan(node: GrassNode, ctx: GeometryContext): FloorplanGeometry {
|
|
const [x, , z] = node.position ?? [0, 0, 0]
|
|
const preset = GRASS_PRESETS[node.preset] ?? GRASS_PRESETS.meadow
|
|
const blade = node.bladeColor ?? preset.bladeColor
|
|
const chrome = chromeOf(ctx)
|
|
const children: FloorplanGeometry[] = [
|
|
{
|
|
kind: 'circle',
|
|
cx: x,
|
|
cy: z,
|
|
r: 0.12,
|
|
fill: blade,
|
|
fillOpacity: 0.5,
|
|
stroke: chrome.stroke ?? blade,
|
|
strokeWidth: 0.02,
|
|
strokeDasharray: '0.06 0.05',
|
|
},
|
|
]
|
|
if (chrome.selected) children.push({ kind: 'move-handle', point: [x, z] })
|
|
return { kind: 'group', children }
|
|
}
|