import { ComponentNature, RealObjectGroup, RealObject } from '@hatiolab/things-scene'; import type { State } from '@hatiolab/things-scene'; import { type Alignment, type Heights, type PlacementArchetype } from '@operato/scene-base'; /** SignalTower 컴포넌트 state */ export interface SignalTowerState extends State { value?: number; blink?: boolean; } export declare class SignalTower3D extends RealObjectGroup { private _lightMeshes; build(): void; updateLights(forceOff?: boolean): void; blinkTick(): void; update(): void; updateDimension(): void; onchange(after: Record, before: Record): void; } declare const Base: any; /** * Signal Tower — fixed indicator extending from the **operation tier** up to * the **signage tier**. * * Real-world signal towers mount on top of equipment / workbench (= operation * tier in this model) and rise up so the lights themselves sit at the * standardized signage / alert mount height (~2m above floor — visibility- * driven, often regulatory). So the tower's *base* is at operation, and its * *top* (= where the colored lamps are) reaches signage: * * - `placement = 'operation'` (semantic: tower base sits at operation tier) * - `defaultDepth = h => h.signage - h.operation` (tower height = the * vertical span between mount and lamp visibility level) * - `computeDefaultZPos` overridden to always return `heights.operation` * (Placeable's default `'operation'`-archetype strategies are * cargo-specific — strategy 2 stacks on a Placeable parent, strategy 3 * falls to the floor when standalone — neither matches a fixed indicator * whose base globally anchors to the operation tier regardless of * parent context). * * The 2D representation is the top-down view (a filled circle); the fill * color reflects the currently active light. When multiple lights are on, * the fill cycles through them in a round-robin (pickActiveColor / * SLICE_PERIOD). When `blink` is true, all lights blank during the off * phase regardless of which are nominally on. * * For rare standalone-pole towers (free-standing from floor), override * `zPos` and `depth` per-instance in the saved state. */ export declare class SignalTower extends Base { state: SignalTowerState; _realObject?: SignalTower3D; private _animRaf; private _lastFrameKey; static placement: PlacementArchetype; static align: Alignment; static defaultDepth: (h: Heights) => number; /** * Override Placeable's default `'operation'`-archetype zPos resolution. * * Placeable treats `placement: 'operation'` as cargo-flow semantics: * - Strategy 2: stack on Placeable parent's top surface * - Strategy 3: fall to floor when standalone (cargo shouldn't float) * * Signal tower is *fixed equipment*, not cargo — its base globally anchors * to `heights.operation` (= equipment-mount height) regardless of parent. */ computeDefaultZPos(_depth: number): number; buildRealObject(): RealObject | undefined; get nature(): ComponentNature; ready(): Promise; dispose(): void; /** * Decide if the per-frame animation loop is needed. * * Required when: * - `blink` mode is on (visual must alternate on/off), OR * - the round-robin time-slice cycle is active (multi-color value) * * Otherwise the visual is static — no RAF needed. */ private _animationNeeded; private _maybeStartAnimation; private _startAnimation; private _stopAnimation; onchange(after: Record, before: Record): void; /** * Override the inherited `fillStyle` getter so the auto-fill done by * `Shape.postrender → drawFill` paints the body in the *currently active * light color* instead of the static `state.fillStyle`. * * Why override the getter (not the postrender)? * `drawFill` (`drawer/fill.ts`) reads the fill color from * `host_component.fillStyle` directly, not from its `style` argument — * so passing `{ fillStyle: ... }` as an override has no effect. Having a * dynamic getter is the simplest hook into the standard pipeline. * * Falls back to the saved `state.fillStyle` when no light is active * (= dark "off" color from the template), so the visual stays coherent * for value=0 / blink-off-phase. */ get fillStyle(): any; /** * Intercept `drawStroke` to drive border color with the same active light * color as the fill. At small canvas sizes the dark default border eats * into the lit area; matching colors keeps the whole circle "lit". * * `drawStroke` (in `component-drawing.ts`) merges the `override` map over * `this.state` and reads `strokeStyle` from the merged object, so we can * inject the active color cleanly via the override path (unlike * `drawFill`, which ignores `override.fillStyle` and reads from the * `fillStyle` getter — hence the parallel getter override above). */ drawStroke(ctx: CanvasRenderingContext2D, override?: Record): void; /** * Set up the body path. Shape's default postrender will fill it (with * our dynamic `fillStyle` getter) and stroke it (with `state.strokeStyle`). */ render(ctx: CanvasRenderingContext2D): void; /** * Append the lit-lamp glow overlay AFTER Shape's postrender has done the * fill/stroke. Glow only when at least one light is active. */ postrender(ctx: CanvasRenderingContext2D): void; } export {};