import { Copy, LockKeyhole, Trash2 } from 'lucide-react'; import { Button, ScrubbableNumberInput, Select, Tooltip } from '../components/ui'; import { MAP_OBJECT_PLACEMENT_LABELS, type EditableMapObject } from './editor/types'; const PARAMETER_LABELS: Record = { sizeX: '尺寸 X(m)', sizeY: '尺寸 Y(m)', sizeZ: '尺寸 Z(m)', radius: '半径(m)', height: '高度(m)', length: '长度(m)', width: '宽度(m)', rise: '抬升(m)', thickness: '厚度(m)', stepDepth: '踏步深度(m)', stepHeight: '踏步高度(m)', count: '踏步数量', }; const SURFACE_PRESETS = { standard: { label: '标准防滑材质', rgb: [0.55, 0.6, 0.68] as const, friction: [1, 0.005, 0.0001] as const, }, concrete: { label: '混凝土', rgb: [0.55, 0.58, 0.62] as const, friction: [1, 0.005, 0.0001] as const, }, rubber: { label: '橡胶', rgb: [0.12, 0.14, 0.16] as const, friction: [1.5, 0.008, 0.0002] as const, }, metal: { label: '金属', rgb: [0.42, 0.48, 0.54] as const, friction: [0.45, 0.003, 0.0001] as const, }, ice: { label: '低摩擦冰面', rgb: [0.58, 0.82, 0.94] as const, friction: [0.08, 0.001, 0.00001] as const, }, } as const; type SurfacePreset = keyof typeof SURFACE_PRESETS; function close(a: number, b: number): boolean { return Math.abs(a - b) < 0.015; } function selectedSurfacePreset(object: EditableMapObject): SurfacePreset | 'custom' { for (const [key, preset] of Object.entries(SURFACE_PRESETS) as [ SurfacePreset, (typeof SURFACE_PRESETS)[SurfacePreset], ][]) { if ( preset.rgb.every((channel, index) => close(channel, object.rgba[index])) && close(preset.friction[0], object.friction[0]) ) return key; } return 'custom'; } function yawDegrees(object: EditableMapObject): number { const [w, x, y, z] = object.pose.quaternion; return (Math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z)) * 180) / Math.PI; } function colorValue(object: EditableMapObject): string { return `#${object.rgba .slice(0, 3) .map((channel) => Math.round(Math.min(1, Math.max(0, channel)) * 255) .toString(16) .padStart(2, '0'), ) .join('')}`; } function Section({ title, children }: { title: string; children: React.ReactNode }) { return (

{title}

{children}
); } export function MapObjectInspector({ object, loading, onUpdate, onDuplicate, onDelete, }: { object: EditableMapObject; loading: boolean; onUpdate: (patch: Partial>) => void; onDuplicate: () => void; onDelete: () => void; }) { const poseLocked = object.placementMode === 'locked'; const updatePosition = (index: number, value: number) => { if (!Number.isFinite(value)) return; const position = [...object.pose.position] as [number, number, number]; position[index] = value; onUpdate({ pose: { ...object.pose, position } }); }; const updateParameter = (key: string, value: number) => { if (!Number.isFinite(value) || value <= 0) return; onUpdate({ parameters: { ...object.parameters, [key]: key === 'count' ? Math.round(value) : value, }, }); }; const updateFriction = (index: number, value: number) => { if (!Number.isFinite(value) || value < 0) return; const friction = [...object.friction] as [number, number, number]; friction[index] = value; onUpdate({ friction }); }; return (
{MAP_OBJECT_PLACEMENT_LABELS[object.placementMode]} {poseLocked &&
{object.pose.position.map((value, index) => ( updatePosition(index, next)} /> ))}
{ const half = (next * Math.PI) / 360; onUpdate({ pose: { ...object.pose, quaternion: [Math.cos(half), 0, 0, Math.sin(half)], }, }); }} />
{Object.entries(object.parameters).map(([key, value]) => ( updateParameter(key, next)} /> ))}
表面材质
onUpdate({ rgba: [object.rgba[0], object.rgba[1], object.rgba[2], alpha], }) } />
摩擦力
{object.friction.map((value, index) => ( updateFriction(index, next)} /> ))}
); }