@operato/scene-storage 10.0.0-beta.48 → 10.0.0-beta.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/box.js +2 -2
  3. package/dist/box.js.map +1 -1
  4. package/dist/index.d.ts +9 -0
  5. package/dist/index.js +6 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/pallet.js +2 -2
  8. package/dist/pallet.js.map +1 -1
  9. package/dist/parcel.js +2 -2
  10. package/dist/parcel.js.map +1 -1
  11. package/dist/picking-station-3d.d.ts +20 -0
  12. package/dist/picking-station-3d.js +162 -0
  13. package/dist/picking-station-3d.js.map +1 -0
  14. package/dist/picking-station.d.ts +50 -0
  15. package/dist/picking-station.js +186 -0
  16. package/dist/picking-station.js.map +1 -0
  17. package/dist/rack-capability.d.ts +11 -0
  18. package/dist/rack-capability.js +25 -0
  19. package/dist/rack-capability.js.map +1 -0
  20. package/dist/rack-grid.d.ts +4 -22
  21. package/dist/rack-grid.js +23 -115
  22. package/dist/rack-grid.js.map +1 -1
  23. package/dist/spot.d.ts +1 -0
  24. package/dist/spot.js +6 -2
  25. package/dist/spot.js.map +1 -1
  26. package/dist/stockpile-3d.d.ts +55 -0
  27. package/dist/stockpile-3d.js +387 -0
  28. package/dist/stockpile-3d.js.map +1 -0
  29. package/dist/stockpile-grid-3d.d.ts +30 -0
  30. package/dist/stockpile-grid-3d.js +301 -0
  31. package/dist/stockpile-grid-3d.js.map +1 -0
  32. package/dist/stockpile-grid.d.ts +85 -0
  33. package/dist/stockpile-grid.js +361 -0
  34. package/dist/stockpile-grid.js.map +1 -0
  35. package/dist/stockpile.d.ts +116 -0
  36. package/dist/stockpile.js +345 -0
  37. package/dist/stockpile.js.map +1 -0
  38. package/dist/storage-rack.d.ts +39 -44
  39. package/dist/storage-rack.js +71 -146
  40. package/dist/storage-rack.js.map +1 -1
  41. package/dist/templates/index.d.ts +80 -0
  42. package/dist/templates/index.js +7 -1
  43. package/dist/templates/index.js.map +1 -1
  44. package/dist/templates/picking-station.d.ts +20 -0
  45. package/dist/templates/picking-station.js +22 -0
  46. package/dist/templates/picking-station.js.map +1 -0
  47. package/dist/templates/stockpile-grid.d.ts +37 -0
  48. package/dist/templates/stockpile-grid.js +38 -0
  49. package/dist/templates/stockpile-grid.js.map +1 -0
  50. package/dist/templates/stockpile.d.ts +29 -0
  51. package/dist/templates/stockpile.js +31 -0
  52. package/dist/templates/stockpile.js.map +1 -0
  53. package/package.json +3 -3
  54. package/src/box.ts +2 -1
  55. package/src/index.ts +14 -0
  56. package/src/pallet.ts +2 -1
  57. package/src/parcel.ts +2 -1
  58. package/src/picking-station-3d.ts +164 -0
  59. package/src/picking-station.ts +220 -0
  60. package/src/rack-capability.ts +26 -0
  61. package/src/rack-grid.ts +24 -108
  62. package/src/spot.ts +15 -1
  63. package/src/stockpile-3d.ts +412 -0
  64. package/src/stockpile-grid-3d.ts +327 -0
  65. package/src/stockpile-grid.ts +408 -0
  66. package/src/stockpile.ts +427 -0
  67. package/src/storage-rack.ts +82 -137
  68. package/src/templates/index.ts +7 -1
  69. package/src/templates/picking-station.ts +23 -0
  70. package/src/templates/stockpile-grid.ts +39 -0
  71. package/src/templates/stockpile.ts +32 -0
  72. package/test/test-rack-capability.ts +51 -0
  73. package/translations/en.json +23 -6
  74. package/translations/ja.json +23 -6
  75. package/translations/ko.json +22 -5
  76. package/translations/ms.json +23 -6
  77. package/translations/zh.json +22 -5
  78. package/tsconfig.tsbuildinfo +1 -1
package/dist/spot.d.ts CHANGED
@@ -8,6 +8,7 @@ export interface SpotState extends State {
8
8
  }
9
9
  declare const Spot_base: any;
10
10
  export default class Spot extends Spot_base {
11
+ _singleSlotId(): string;
11
12
  state: SpotState;
12
13
  _realObject?: Spot3D;
13
14
  static placement: PlacementArchetype;
package/dist/spot.js CHANGED
@@ -30,7 +30,7 @@
30
30
  */
31
31
  import { __decorate } from "tslib";
32
32
  import { ContainerAbstract, sceneComponent } from '@hatiolab/things-scene';
33
- import { CarrierHolder, Placeable } from '@operato/scene-base';
33
+ import { CarrierHolder, Placeable, SingleSlotHolder } from '@operato/scene-base';
34
34
  import { Spot3D } from './spot-3d.js';
35
35
  const NATURE = {
36
36
  mutable: false,
@@ -49,7 +49,9 @@ const NATURE = {
49
49
  // `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),
50
50
  // which forces `isHTMLElement(): true` and trips the 3D pipeline's
51
51
  // addObject DOM-skip gate. Spot is purely 3D.
52
- let Spot = class Spot extends CarrierHolder(Placeable(ContainerAbstract)) {
52
+ let Spot = class Spot extends SingleSlotHolder()(CarrierHolder(Placeable(ContainerAbstract))) {
53
+ // SingleSlotHolder hook override — Spot 의 slot id 'pad'.
54
+ _singleSlotId() { return SPOT_SLOT_ID; }
53
55
  static placement = 'floor';
54
56
  static align = 'bottom';
55
57
  static defaultDepth = (_h) => 2; // a thin pad
@@ -138,6 +140,8 @@ Spot = __decorate([
138
140
  sceneComponent('spot')
139
141
  ], Spot);
140
142
  export default Spot;
143
+ /** Spot 의 유일 virtual slot id. */
144
+ const SPOT_SLOT_ID = 'pad';
141
145
  function resolveDepth(c) {
142
146
  const eff = c._realObject?.effectiveDepth;
143
147
  if (typeof eff === 'number' && Number.isFinite(eff))
package/dist/spot.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"spot.js","sourceRoot":"","sources":["../src/spot.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Spot — virtual pickup / drop zone.\n *\n * A modeling-time anchor for \"this is where things land\" — the destination\n * of a robot arm pick-and-place, the slot of an AGV stop, the staging\n * zone next to a conveyor. Spot itself does not move and does\n * not perform any logistics action; it only marks a location and accepts\n * carrier components as children.\n *\n * Visual identity:\n * - 2D: outlined rectangle with corner \"L\" marks (so it reads as a\n * virtual zone, not a solid object).\n * - 3D: a thin translucent floor pad — only the floor of the conceptual\n * box is rendered, the side walls are absent.\n *\n * Standard things-scene properties used (no component-specific extras —\n * keep the property-panel UX uniform with other components):\n * - `fillStyle` — pad / outline color (sole color source)\n * - `strokeStyle` — outline color override (defaults to fillStyle)\n * - `lineWidth` / `lineDash` — outline stroke style\n * - `alpha` — overall transparency, framework-applied\n * - `text` / `fontColor` / `fontSize` / `fontFamily` / `bold` / `italic`\n * — label rendered by the standard text pipeline\n * - `material3d` (3D) — metalness / roughness / castShadow / receiveShadow\n *\n * Role: `CarrierHolder` — accepts any Carrier as a child and lays it on\n * the top face of the pad (overrides default attachPointFor).\n */\n\nimport { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n Placeable,\n type AttachFrame,\n type Alignment,\n type Heights,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Spot3D } from './spot-3d.js'\n\n/** Spot 컴포넌트 state */\nexport interface SpotState extends State {\n // Spot has no component-specific state — it uses standard fillStyle /\n // strokeStyle / lineWidth / alpha / text / font* / material3d.\n material3d?: Material3D\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n // No component-specific properties — fillStyle / strokeStyle / lineWidth /\n // alpha / text / font* are framework-standard, surfaced by the property\n // panel automatically.\n properties: [],\n help: 'scene/component/spot'\n}\n\n// ContainerAbstract base — Spot accepts carrier children (parcel/box/pallet/...).\n// CarrierHolder mixin only publishes the attach-point hook; the actual\n// child-list management comes from things-scene's container abstract.\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Spot is purely 3D.\n@sceneComponent('spot')\nexport default class Spot extends CarrierHolder(Placeable(ContainerAbstract)) {\n declare state: SpotState\n declare _realObject?: Spot3D\n\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (_h: Heights) => 2 // a thin pad\n\n get nature(): ComponentNature {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — outlined rectangle + corner L marks. The pad body is drawn with a\n * fixed low alpha (0.15) on top of the user's `fillStyle` so the zone reads\n * as virtual even when fillStyle is fully opaque. Outline + corner marks\n * use `strokeStyle` (or fall back to `fillStyle`) at the user's `lineWidth`\n * and `lineDash`. The text label is drawn by the framework's standard\n * postrender pipeline using `text` / `fontColor` / `fontSize` / etc.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { left = 0, top = 0, width = 100, height = 100 } = this.state\n const fillStyle = (this.state.fillStyle as string) || '#3a8fbd'\n const strokeStyle = (this.state.strokeStyle as string) || fillStyle\n const lineWidth = numOr(this.state.lineWidth, 1)\n const lineDashStyle = String(this.state.lineDash ?? 'dash')\n\n // ── Pad body (fixed-low-alpha tint of fillStyle) ───────────────────\n ctx.save()\n ctx.fillStyle = fillStyle\n ctx.globalAlpha = 0.15\n ctx.fillRect(left, top, width, height)\n ctx.restore()\n\n // ── Outline ────────────────────────────────────────────────────────\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = lineWidth\n applyLineDash(ctx, lineDashStyle, lineWidth)\n ctx.strokeRect(\n left + lineWidth / 2,\n top + lineWidth / 2,\n width - lineWidth,\n height - lineWidth\n )\n ctx.setLineDash([])\n ctx.restore()\n\n // ── Corner L marks (solid, slightly heavier than outline) ──────────\n const ml = Math.min(width, height) * 0.18\n const cornerW = Math.max(lineWidth * 1.5, 1.5)\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = cornerW\n for (const [cx, cy, sx, sy] of [\n [left, top, 1, 1],\n [left + width, top, -1, 1],\n [left + width, top + height, -1, -1],\n [left, top + height, 1, -1]\n ] as [number, number, number, number][]) {\n ctx.beginPath()\n ctx.moveTo(cx + sx * ml, cy)\n ctx.lineTo(cx, cy)\n ctx.lineTo(cx, cy + sy * ml)\n ctx.stroke()\n }\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Spot3D(this)\n }\n\n /**\n * Mount carriers on the TOP of the pad (Spot3D's `getAttachFrame` is\n * already at pad-top in spot-local). Then lift the carrier by its\n * own halfDepth so the carrier's BOTTOM rests ON the pad surface, not\n * its volumetric center — without this lift, half the carrier would\n * sink below the pad / floor.\n *\n * Reads `_realObject.effectiveDepth` first (the framework-resolved\n * value, accounting for `static defaultDepth` and parent context),\n * falling back to raw `state.depth` for components built before\n * RealObject creation.\n */\n attachPointFor(carrier: Component): AttachFrame | null {\n const ro = this._realObject\n const frame = ro?.getAttachFrame?.()\n if (!frame) return null\n const carrierDepth = resolveDepth(carrier)\n return {\n attach: frame,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n}\n\nfunction resolveDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n return numOr((c as any)?.state?.depth, 0)\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n\n/**\n * Map a things-scene `lineDash` string to a Canvas dash pattern. Mirrors\n * the keys understood by things-scene's `drawer/stroke.ts` so users see\n * consistent options across components. Unknown strings fall through to\n * a plain dashed pattern instead of throwing on setLineDash.\n */\nfunction applyLineDash(ctx: CanvasRenderingContext2D, style: string, lw: number) {\n switch (style) {\n case 'solid':\n ctx.setLineDash([])\n return\n case 'round-dot':\n ctx.setLineDash([0.1, lw * 2])\n ctx.lineCap = 'round'\n return\n case 'square-dot':\n ctx.setLineDash([lw, lw])\n return\n case 'long-dash':\n ctx.setLineDash([lw * 6, lw * 3])\n return\n case 'dash-dot':\n ctx.setLineDash([lw * 4, lw * 2, lw, lw * 2])\n return\n case 'dash':\n default:\n ctx.setLineDash([lw * 4, lw * 1.5])\n return\n }\n}\n"]}
1
+ {"version":3,"file":"spot.js","sourceRoot":"","sources":["../src/spot.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Spot — virtual pickup / drop zone.\n *\n * A modeling-time anchor for \"this is where things land\" — the destination\n * of a robot arm pick-and-place, the slot of an AGV stop, the staging\n * zone next to a conveyor. Spot itself does not move and does\n * not perform any logistics action; it only marks a location and accepts\n * carrier components as children.\n *\n * Visual identity:\n * - 2D: outlined rectangle with corner \"L\" marks (so it reads as a\n * virtual zone, not a solid object).\n * - 3D: a thin translucent floor pad — only the floor of the conceptual\n * box is rendered, the side walls are absent.\n *\n * Standard things-scene properties used (no component-specific extras —\n * keep the property-panel UX uniform with other components):\n * - `fillStyle` — pad / outline color (sole color source)\n * - `strokeStyle` — outline color override (defaults to fillStyle)\n * - `lineWidth` / `lineDash` — outline stroke style\n * - `alpha` — overall transparency, framework-applied\n * - `text` / `fontColor` / `fontSize` / `fontFamily` / `bold` / `italic`\n * — label rendered by the standard text pipeline\n * - `material3d` (3D) — metalness / roughness / castShadow / receiveShadow\n *\n * Role: `CarrierHolder` — accepts any Carrier as a child and lays it on\n * the top face of the pad (overrides default attachPointFor).\n */\n\nimport { Component, ComponentNature, ContainerAbstract, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n Placeable,\n SingleSlotHolder,\n type AttachFrame,\n type Alignment,\n type Heights,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Spot3D } from './spot-3d.js'\n\n/** Spot 컴포넌트 state */\nexport interface SpotState extends State {\n // Spot has no component-specific state — it uses standard fillStyle /\n // strokeStyle / lineWidth / alpha / text / font* / material3d.\n material3d?: Material3D\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n // No component-specific properties — fillStyle / strokeStyle / lineWidth /\n // alpha / text / font* are framework-standard, surfaced by the property\n // panel automatically.\n properties: [],\n help: 'scene/component/spot'\n}\n\n// ContainerAbstract base — Spot accepts carrier children (parcel/box/pallet/...).\n// CarrierHolder mixin only publishes the attach-point hook; the actual\n// child-list management comes from things-scene's container abstract.\n//\n// `ContainerAbstract` (not `Container`) — Container = MixinHTMLElement(ContainerAbstract),\n// which forces `isHTMLElement(): true` and trips the 3D pipeline's\n// addObject DOM-skip gate. Spot is purely 3D.\n@sceneComponent('spot')\nexport default class Spot extends SingleSlotHolder()(\n CarrierHolder(Placeable(ContainerAbstract))\n) {\n // SingleSlotHolder hook override — Spot 의 slot id 'pad'.\n _singleSlotId() { return SPOT_SLOT_ID }\n declare state: SpotState\n declare _realObject?: Spot3D\n\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (_h: Heights) => 2 // a thin pad\n\n get nature(): ComponentNature {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — outlined rectangle + corner L marks. The pad body is drawn with a\n * fixed low alpha (0.15) on top of the user's `fillStyle` so the zone reads\n * as virtual even when fillStyle is fully opaque. Outline + corner marks\n * use `strokeStyle` (or fall back to `fillStyle`) at the user's `lineWidth`\n * and `lineDash`. The text label is drawn by the framework's standard\n * postrender pipeline using `text` / `fontColor` / `fontSize` / etc.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { left = 0, top = 0, width = 100, height = 100 } = this.state\n const fillStyle = (this.state.fillStyle as string) || '#3a8fbd'\n const strokeStyle = (this.state.strokeStyle as string) || fillStyle\n const lineWidth = numOr(this.state.lineWidth, 1)\n const lineDashStyle = String(this.state.lineDash ?? 'dash')\n\n // ── Pad body (fixed-low-alpha tint of fillStyle) ───────────────────\n ctx.save()\n ctx.fillStyle = fillStyle\n ctx.globalAlpha = 0.15\n ctx.fillRect(left, top, width, height)\n ctx.restore()\n\n // ── Outline ────────────────────────────────────────────────────────\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = lineWidth\n applyLineDash(ctx, lineDashStyle, lineWidth)\n ctx.strokeRect(\n left + lineWidth / 2,\n top + lineWidth / 2,\n width - lineWidth,\n height - lineWidth\n )\n ctx.setLineDash([])\n ctx.restore()\n\n // ── Corner L marks (solid, slightly heavier than outline) ──────────\n const ml = Math.min(width, height) * 0.18\n const cornerW = Math.max(lineWidth * 1.5, 1.5)\n ctx.save()\n ctx.strokeStyle = strokeStyle\n ctx.lineWidth = cornerW\n for (const [cx, cy, sx, sy] of [\n [left, top, 1, 1],\n [left + width, top, -1, 1],\n [left + width, top + height, -1, -1],\n [left, top + height, 1, -1]\n ] as [number, number, number, number][]) {\n ctx.beginPath()\n ctx.moveTo(cx + sx * ml, cy)\n ctx.lineTo(cx, cy)\n ctx.lineTo(cx, cy + sy * ml)\n ctx.stroke()\n }\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Spot3D(this)\n }\n\n /**\n * Mount carriers on the TOP of the pad (Spot3D's `getAttachFrame` is\n * already at pad-top in spot-local). Then lift the carrier by its\n * own halfDepth so the carrier's BOTTOM rests ON the pad surface, not\n * its volumetric center — without this lift, half the carrier would\n * sink below the pad / floor.\n *\n * Reads `_realObject.effectiveDepth` first (the framework-resolved\n * value, accounting for `static defaultDepth` and parent context),\n * falling back to raw `state.depth` for components built before\n * RealObject creation.\n */\n attachPointFor(carrier: Component): AttachFrame | null {\n const ro = this._realObject\n const frame = ro?.getAttachFrame?.()\n if (!frame) return null\n const carrierDepth = resolveDepth(carrier)\n return {\n attach: frame,\n localPosition: { x: 0, y: carrierDepth / 2, z: 0 }\n }\n }\n\n // SlottedHolder duck (slotIds / hasCarrierAt / canReceiveAt / occupiedSlotIds /\n // emptySlotIds / obtainCarrier / receiveAt / accept / receive / slotTargetAt /\n // getSlotAttachObject3d) — SingleSlotHolder mixin 제공.\n // virtual location handoff buffer 동작: receiveAt 가 reparent (components +\n // 3D) 로 carrier 를 자식으로 유지 → 다음 mover 가 obtainCarrier 로 승계.\n}\n\n/** Spot 의 유일 virtual slot id. */\nconst SPOT_SLOT_ID = 'pad'\n\nfunction resolveDepth(c: Component): number {\n const eff = (c as any)._realObject?.effectiveDepth\n if (typeof eff === 'number' && Number.isFinite(eff)) return eff\n return numOr((c as any)?.state?.depth, 0)\n}\n\nfunction numOr(v: unknown, dflt: number): number {\n return typeof v === 'number' && Number.isFinite(v) ? v : dflt\n}\n\n/**\n * Map a things-scene `lineDash` string to a Canvas dash pattern. Mirrors\n * the keys understood by things-scene's `drawer/stroke.ts` so users see\n * consistent options across components. Unknown strings fall through to\n * a plain dashed pattern instead of throwing on setLineDash.\n */\nfunction applyLineDash(ctx: CanvasRenderingContext2D, style: string, lw: number) {\n switch (style) {\n case 'solid':\n ctx.setLineDash([])\n return\n case 'round-dot':\n ctx.setLineDash([0.1, lw * 2])\n ctx.lineCap = 'round'\n return\n case 'square-dot':\n ctx.setLineDash([lw, lw])\n return\n case 'long-dash':\n ctx.setLineDash([lw * 6, lw * 3])\n return\n case 'dash-dot':\n ctx.setLineDash([lw * 4, lw * 2, lw, lw * 2])\n return\n case 'dash':\n default:\n ctx.setLineDash([lw * 4, lw * 1.5])\n return\n }\n}\n"]}
@@ -0,0 +1,55 @@
1
+ import * as THREE from 'three';
2
+ import { RealObjectGroup } from '@hatiolab/things-scene';
3
+ export declare class Stockpile3D extends RealObjectGroup {
4
+ private _carriersGroup?;
5
+ private _padMesh?;
6
+ /** pad 의 외곽선 — strokeStyle 표현 (2D dashed outline 의 3D 대응). */
7
+ private _padOutline?;
8
+ /** record.id → 해당 carrier mesh 매핑 — popup anchor / tether 의 대상으로 사용. */
9
+ private _meshByRecordId;
10
+ /** Legend 매핑된 색을 material 로 캐시 — 같은 색 record 들은 material 공유 (drawcall ↓). */
11
+ private _materialByColor;
12
+ private _getMaterialForColor;
13
+ private _disposeMaterialCache;
14
+ build(): void;
15
+ update(): void;
16
+ /**
17
+ * carrier attach point.
18
+ * - slotId 가 record.id 와 매칭되면 그 record 의 mesh 반환 → popup tether 가 정확히
19
+ * 해당 stock 에 연결.
20
+ * - 아니면 pad 반환 (단일 'pile' slot, mover place 의 default anchor).
21
+ */
22
+ getAttachFrame(slotId?: string): THREE.Object3D | undefined;
23
+ /** state.fillStyle → THREE.Color. 잘못된 값/없으면 default. capacity 점유율에 따른
24
+ * 색 변경은 강요하지 않음 — 필요하면 사용자가 mapping(eval)으로 fillStyle 을 조정. */
25
+ private _padColor;
26
+ /** state.strokeStyle → THREE.Color (pad 외곽선 색). 미설정 시 fillStyle 따라가되
27
+ * 대비 위해 약간 어둡게 */
28
+ private _strokeColor;
29
+ private _applyStrokeColor;
30
+ /** state.fillStyle 변경 즉시 반영 — pad material 의 color + emissive 갱신. */
31
+ private _applyPadColor;
32
+ private _rebuildCarriers;
33
+ /** record.id 로 해당 carrier mesh 직접 조회 — popup tether anchor 용. */
34
+ getMeshByRecordId(recordId: string): THREE.Mesh | undefined;
35
+ /**
36
+ * stackPattern 별 각 carrier 의 local 좌표(영역 중심 기준). y 는 pad 위에 쌓이는
37
+ * 높이(cd 단위), x/z 는 영역 평면 위 분포.
38
+ */
39
+ private _computeStackPositions;
40
+ /** carrierPreset 별 geometry. drum 은 cylinder, 나머지는 box. */
41
+ private _carrierGeometry;
42
+ /**
43
+ * state.alpha 변경 시 pad + carriers 의 material opacity 즉시 반영.
44
+ * pad 는 원래 0.55 base opacity 위에 alpha 곱 (영역 표시가 너무 진해지지 않도록).
45
+ * carriers 는 alpha 그대로 (단순한 곱은 fully opaque 이 1, 반투명이 0).
46
+ */
47
+ updateAlpha(): void;
48
+ updateDimension(): void;
49
+ /**
50
+ * state 변경 시 즉시 3D 갱신 — property panel 의 수정이 바로 반영되도록.
51
+ * - width/height → pad geometry 재생성 + carriers 재계산 (updateDimension)
52
+ * - 적치/외형 관련 키 → carriers 만 재계산 (_rebuildCarriers)
53
+ */
54
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
55
+ }
@@ -0,0 +1,387 @@
1
+ /*
2
+ * Copyright © HatioLab Inc. All rights reserved.
3
+ *
4
+ * Stockpile3D — 평치 영역의 3D 시각화.
5
+ *
6
+ * - floor pad (얇은 box, 영역 footprint).
7
+ * - records.length 만큼 가상 carrier mesh 를 *_stackPattern_* 으로 자동 배치.
8
+ * - mesh geometry / material 은 *_carrierPreset_* 별.
9
+ *
10
+ * 핵심 — 실제 carrier 컴포넌트를 자식으로 두지 않고 *_가상 mesh_* 만 그려서 다수
11
+ * 적치를 가볍게 표현. records 변경 시 update() 가 호출되어 mesh 가 재배치된다.
12
+ */
13
+ import * as THREE from 'three';
14
+ import { RealObjectGroup } from '@hatiolab/things-scene';
15
+ const PAD_DEPTH = 2;
16
+ /** carrierPreset 별 default 색 (state.carrierWidth/Height/Depth 미지정 시 크기와 함께). */
17
+ const PRESET_COLOR = {
18
+ box: 0xb87333, // kraft brown
19
+ pallet: 0x8b6f47, // 짙은 나무
20
+ drum: 0x556679, // 금속/blue-grey
21
+ sack: 0xd4c89a, // beige 천
22
+ crate: 0x9a7548, // 나무 상자
23
+ bale: 0xa89968 // 압축 베일
24
+ };
25
+ const PRESET_DEFAULT_SIZE = {
26
+ box: { w: 26, h: 26, d: 20 },
27
+ pallet: { w: 40, h: 30, d: 15 },
28
+ drum: { w: 24, h: 24, d: 30 },
29
+ sack: { w: 30, h: 28, d: 18 },
30
+ crate: { w: 30, h: 30, d: 22 },
31
+ bale: { w: 32, h: 30, d: 18 }
32
+ };
33
+ export class Stockpile3D extends RealObjectGroup {
34
+ _carriersGroup;
35
+ _padMesh;
36
+ /** pad 의 외곽선 — strokeStyle 표현 (2D dashed outline 의 3D 대응). */
37
+ _padOutline;
38
+ /** record.id → 해당 carrier mesh 매핑 — popup anchor / tether 의 대상으로 사용. */
39
+ _meshByRecordId = new Map();
40
+ /** Legend 매핑된 색을 material 로 캐시 — 같은 색 record 들은 material 공유 (drawcall ↓). */
41
+ _materialByColor = new Map();
42
+ _getMaterialForColor(colorHex) {
43
+ let m = this._materialByColor.get(colorHex);
44
+ if (!m) {
45
+ m = new THREE.MeshStandardMaterial({ color: colorHex, roughness: 0.7 });
46
+ this._materialByColor.set(colorHex, m);
47
+ }
48
+ return m;
49
+ }
50
+ _disposeMaterialCache() {
51
+ for (const m of this._materialByColor.values())
52
+ m.dispose();
53
+ this._materialByColor.clear();
54
+ }
55
+ build() {
56
+ super.build();
57
+ const state = this.component.state;
58
+ const w = state.width ?? 100;
59
+ const h = state.height ?? 100;
60
+ // ── floor pad — 영역 표시 (얇은 box) ──────────────────────────────
61
+ const padGeom = new THREE.BoxGeometry(w, PAD_DEPTH, h);
62
+ const padColor = this._padColor();
63
+ const padMat = new THREE.MeshStandardMaterial({
64
+ color: padColor, roughness: 0.85, transparent: true, opacity: 0.55,
65
+ emissive: padColor, emissiveIntensity: 0.05
66
+ });
67
+ this._padMesh = new THREE.Mesh(padGeom, padMat);
68
+ this._padMesh.position.y = PAD_DEPTH / 2;
69
+ this._padMesh.receiveShadow = true;
70
+ this.object3d.add(this._padMesh);
71
+ // pad 외곽선 — strokeStyle 3D 표현. 2D 의 dashed outline 과 같은 의도.
72
+ const outlineColor = this._strokeColor();
73
+ const outlineMat = new THREE.LineBasicMaterial({ color: outlineColor });
74
+ this._padOutline = new THREE.LineSegments(new THREE.EdgesGeometry(padGeom), outlineMat);
75
+ this._padOutline.position.y = PAD_DEPTH / 2;
76
+ this.object3d.add(this._padOutline);
77
+ // ── carriers group — records 기반 자동 적치 ─────────────────────
78
+ this._carriersGroup = new THREE.Group();
79
+ this._carriersGroup.position.y = PAD_DEPTH;
80
+ this.object3d.add(this._carriersGroup);
81
+ this._rebuildCarriers();
82
+ }
83
+ update() {
84
+ super.update();
85
+ this._rebuildCarriers();
86
+ }
87
+ /**
88
+ * carrier attach point.
89
+ * - slotId 가 record.id 와 매칭되면 그 record 의 mesh 반환 → popup tether 가 정확히
90
+ * 해당 stock 에 연결.
91
+ * - 아니면 pad 반환 (단일 'pile' slot, mover place 의 default anchor).
92
+ */
93
+ getAttachFrame(slotId) {
94
+ if (slotId) {
95
+ const mesh = this._meshByRecordId.get(slotId);
96
+ if (mesh)
97
+ return mesh;
98
+ }
99
+ return this._padMesh;
100
+ }
101
+ /** state.fillStyle → THREE.Color. 잘못된 값/없으면 default. capacity 점유율에 따른
102
+ * 색 변경은 강요하지 않음 — 필요하면 사용자가 mapping(eval)으로 fillStyle 을 조정. */
103
+ _padColor() {
104
+ const raw = this.component.state?.fillStyle;
105
+ if (typeof raw === 'string' && raw.length > 0) {
106
+ try {
107
+ return new THREE.Color(raw);
108
+ }
109
+ catch { /* fallthrough */ }
110
+ }
111
+ return new THREE.Color(0xc89c5c);
112
+ }
113
+ /** state.strokeStyle → THREE.Color (pad 외곽선 색). 미설정 시 fillStyle 따라가되
114
+ * 대비 위해 약간 어둡게 */
115
+ _strokeColor() {
116
+ const raw = this.component.state?.strokeStyle;
117
+ if (typeof raw === 'string' && raw.length > 0) {
118
+ try {
119
+ return new THREE.Color(raw);
120
+ }
121
+ catch { /* fallthrough */ }
122
+ }
123
+ return this._padColor().clone().multiplyScalar(0.6);
124
+ }
125
+ _applyStrokeColor() {
126
+ if (!this._padOutline)
127
+ return;
128
+ const m = this._padOutline.material;
129
+ m.color.copy(this._strokeColor());
130
+ m.needsUpdate = true;
131
+ }
132
+ /** state.fillStyle 변경 즉시 반영 — pad material 의 color + emissive 갱신. */
133
+ _applyPadColor() {
134
+ if (!this._padMesh)
135
+ return;
136
+ const m = this._padMesh.material;
137
+ const c = this._padColor();
138
+ m.color.copy(c);
139
+ m.emissive.copy(c);
140
+ m.needsUpdate = true;
141
+ }
142
+ _rebuildCarriers() {
143
+ const g = this._carriersGroup;
144
+ if (!g)
145
+ return;
146
+ // 기존 mesh 모두 제거 (geometry/material 은 공유돼 dispose 안 함)
147
+ while (g.children.length > 0)
148
+ g.remove(g.children[0]);
149
+ const stockpile = this.component;
150
+ const count = stockpile.records.length;
151
+ if (count === 0)
152
+ return;
153
+ const state = stockpile.state;
154
+ const areaW = state.width ?? 100;
155
+ const areaH = state.height ?? 100;
156
+ const pattern = state.stackPattern ?? 'row';
157
+ const preset = state.carrierPreset ?? 'box';
158
+ // carrier 크기 — 명시값 우선, 없으면 preset default
159
+ const def = PRESET_DEFAULT_SIZE[preset] ?? PRESET_DEFAULT_SIZE.box;
160
+ const cw = state.carrierWidth ?? def.w;
161
+ const ch = state.carrierHeight ?? def.h;
162
+ const cd = state.carrierDepth ?? def.d;
163
+ const gap = state.carrierGap ?? 10; // 평면(xz) cell 간격 — 0 이면 딱 붙음. carrier 크기 대비 충분히 분리되도록 default 10.
164
+ const geom = this._carrierGeometry(preset, cw, ch, cd);
165
+ // record 별로 색이 다를 수 있어(Legend 매핑) material 을 record 별로 생성.
166
+ // 같은 색은 material 캐시로 공유해 dispose 비용 / drawcall 최적화.
167
+ const presetDefaultColor = PRESET_COLOR[preset] ?? PRESET_COLOR.box;
168
+ this._disposeMaterialCache();
169
+ const resolveColor = (record) => {
170
+ const legendColor = stockpile.resolveLegendColor?.(record);
171
+ if (typeof legendColor === 'string' && legendColor.length > 0) {
172
+ try {
173
+ return new THREE.Color(legendColor).getHex();
174
+ }
175
+ catch { /* fallthrough */ }
176
+ }
177
+ return presetDefaultColor;
178
+ };
179
+ const positions = this._computeStackPositions(pattern, count, areaW, areaH, cw, ch, cd, gap, state.stackHeightLimit);
180
+ this._meshByRecordId.clear();
181
+ const records = stockpile.records;
182
+ for (let i = 0; i < positions.length; i++) {
183
+ const p = positions[i];
184
+ const record = records[i];
185
+ const colorHex = resolveColor(record);
186
+ const mat = this._getMaterialForColor(colorHex);
187
+ const mesh = new THREE.Mesh(geom, mat);
188
+ mesh.position.set(p.x, p.y, p.z);
189
+ mesh.castShadow = true;
190
+ mesh.receiveShadow = true;
191
+ // raycast hit → record 식별을 위해 userData 에 recordId 저장 + Map 갱신.
192
+ const rid = records[i]?.id;
193
+ if (rid != null) {
194
+ mesh.userData.recordId = String(rid);
195
+ this._meshByRecordId.set(String(rid), mesh);
196
+ }
197
+ g.add(mesh);
198
+ }
199
+ }
200
+ /** record.id 로 해당 carrier mesh 직접 조회 — popup tether anchor 용. */
201
+ getMeshByRecordId(recordId) {
202
+ return this._meshByRecordId.get(recordId);
203
+ }
204
+ /**
205
+ * stackPattern 별 각 carrier 의 local 좌표(영역 중심 기준). y 는 pad 위에 쌓이는
206
+ * 높이(cd 단위), x/z 는 영역 평면 위 분포.
207
+ */
208
+ _computeStackPositions(pattern, count, areaW, areaH, cw, ch, cd, gap, heightLimit) {
209
+ const out = [];
210
+ // column — 한 자리에 수직 적층
211
+ if (pattern === 'column') {
212
+ const layers = heightLimit ?? count;
213
+ for (let i = 0; i < count; i++) {
214
+ const layer = Math.min(i, layers - 1);
215
+ out.push({ x: 0, y: layer * cd + cd / 2, z: 0 });
216
+ }
217
+ return out;
218
+ }
219
+ // 격자 cell — 행/열 수. cell stride = carrier + gap (인접 carrier 사이 분리).
220
+ const stridX = cw + gap;
221
+ const stridZ = ch + gap;
222
+ const cols = Math.max(1, Math.floor((areaW + gap) / stridX));
223
+ const rows = Math.max(1, Math.floor((areaH + gap) / stridZ));
224
+ const cellsPerLayer = cols * rows;
225
+ // pyramid — 위로 갈수록 행/열 -1 씩 (사다리꼴)
226
+ if (pattern === 'pyramid') {
227
+ let remaining = count;
228
+ let layer = 0;
229
+ while (remaining > 0) {
230
+ const lcols = Math.max(1, cols - layer);
231
+ const lrows = Math.max(1, rows - layer);
232
+ const cellsThisLayer = lcols * lrows;
233
+ const take = Math.min(remaining, cellsThisLayer);
234
+ for (let i = 0; i < take; i++) {
235
+ const c = i % lcols;
236
+ const r = Math.floor(i / lcols);
237
+ // 가운데 정렬 — 줄어든 만큼 offset
238
+ const offsetCol = (cols - lcols) / 2;
239
+ const offsetRow = (rows - lrows) / 2;
240
+ const x = -areaW / 2 + (c + offsetCol + 0.5) * stridX;
241
+ const z = -areaH / 2 + (r + offsetRow + 0.5) * stridZ;
242
+ out.push({ x, y: layer * cd + cd / 2, z });
243
+ }
244
+ remaining -= take;
245
+ layer++;
246
+ if (lcols === 1 && lrows === 1) {
247
+ // 정상에 도달 — 남은 건 column 처럼 위로
248
+ for (let i = 0; i < remaining; i++) {
249
+ out.push({ x: 0, y: (layer + i) * cd + cd / 2, z: 0 });
250
+ }
251
+ break;
252
+ }
253
+ if (heightLimit && layer >= heightLimit)
254
+ break;
255
+ }
256
+ return out;
257
+ }
258
+ // pile — 자연 더미. row 와 비슷하되 가장자리는 약간 옆으로 흘러내림 효과
259
+ // (간단 구현 — 단마다 약간 random offset).
260
+ if (pattern === 'pile') {
261
+ // pseudo-random (인덱스 기반, deterministic)
262
+ const rng = (i) => ((Math.sin(i * 12.9898) * 43758.5453) % 1 + 1) % 1;
263
+ for (let i = 0; i < count; i++) {
264
+ const layer = Math.floor(i / cellsPerLayer);
265
+ const idx = i % cellsPerLayer;
266
+ const c = idx % cols;
267
+ const r = Math.floor(idx / cols);
268
+ const jitterX = (rng(i * 2) - 0.5) * cw * 0.2;
269
+ const jitterZ = (rng(i * 2 + 1) - 0.5) * ch * 0.2;
270
+ const x = -areaW / 2 + (c + 0.5) * stridX + jitterX;
271
+ const z = -areaH / 2 + (r + 0.5) * stridZ + jitterZ;
272
+ out.push({ x, y: layer * cd + cd / 2, z });
273
+ if (heightLimit && layer >= heightLimit - 1 && idx === cellsPerLayer - 1)
274
+ break;
275
+ }
276
+ return out;
277
+ }
278
+ // row (default) / staggered — 격자 적층
279
+ for (let i = 0; i < count; i++) {
280
+ const layer = Math.floor(i / cellsPerLayer);
281
+ if (heightLimit && layer >= heightLimit)
282
+ break;
283
+ const idx = i % cellsPerLayer;
284
+ const c = idx % cols;
285
+ const r = Math.floor(idx / cols);
286
+ let x = -areaW / 2 + (c + 0.5) * stridX;
287
+ const z = -areaH / 2 + (r + 0.5) * stridZ;
288
+ // staggered — 홀수 layer 는 1/2 cell offset (벽돌식)
289
+ if (pattern === 'staggered' && layer % 2 === 1) {
290
+ x += stridX / 2;
291
+ }
292
+ out.push({ x, y: layer * cd + cd / 2, z });
293
+ }
294
+ return out;
295
+ }
296
+ /** carrierPreset 별 geometry. drum 은 cylinder, 나머지는 box. */
297
+ _carrierGeometry(preset, w, h, d) {
298
+ if (preset === 'drum') {
299
+ const radius = Math.min(w, h) / 2;
300
+ return new THREE.CylinderGeometry(radius, radius, d, 16);
301
+ }
302
+ // sack/bale — 약간 작게 (자연스러운 형태)
303
+ if (preset === 'sack' || preset === 'bale') {
304
+ return new THREE.BoxGeometry(w * 0.92, d * 0.92, h * 0.92);
305
+ }
306
+ return new THREE.BoxGeometry(w, d, h);
307
+ }
308
+ /**
309
+ * state.alpha 변경 시 pad + carriers 의 material opacity 즉시 반영.
310
+ * pad 는 원래 0.55 base opacity 위에 alpha 곱 (영역 표시가 너무 진해지지 않도록).
311
+ * carriers 는 alpha 그대로 (단순한 곱은 fully opaque 이 1, 반투명이 0).
312
+ */
313
+ updateAlpha() {
314
+ const state = this.component.state;
315
+ const alpha = typeof state.alpha === 'number' ? state.alpha : 1;
316
+ if (this._padMesh) {
317
+ const m = this._padMesh.material;
318
+ m.opacity = 0.55 * alpha;
319
+ m.transparent = m.opacity < 1;
320
+ m.needsUpdate = true;
321
+ }
322
+ if (this._carriersGroup) {
323
+ for (const child of this._carriersGroup.children) {
324
+ const mesh = child;
325
+ const m = mesh.material;
326
+ if (!m)
327
+ continue;
328
+ m.opacity = alpha;
329
+ m.transparent = alpha < 1;
330
+ m.needsUpdate = true;
331
+ }
332
+ }
333
+ }
334
+ updateDimension() {
335
+ // width/height 변경 시 pad geometry + outline + carriers 재계산
336
+ if (this._padMesh) {
337
+ const state = this.component.state;
338
+ const w = state.width ?? 100;
339
+ const h = state.height ?? 100;
340
+ this._padMesh.geometry.dispose();
341
+ this._padMesh.geometry = new THREE.BoxGeometry(w, PAD_DEPTH, h);
342
+ if (this._padOutline) {
343
+ this._padOutline.geometry.dispose();
344
+ this._padOutline.geometry = new THREE.EdgesGeometry(this._padMesh.geometry);
345
+ }
346
+ }
347
+ this._rebuildCarriers();
348
+ }
349
+ /**
350
+ * state 변경 시 즉시 3D 갱신 — property panel 의 수정이 바로 반영되도록.
351
+ * - width/height → pad geometry 재생성 + carriers 재계산 (updateDimension)
352
+ * - 적치/외형 관련 키 → carriers 만 재계산 (_rebuildCarriers)
353
+ */
354
+ onchange(after, before) {
355
+ if ('width' in after || 'height' in after) {
356
+ this.updateDimension();
357
+ return;
358
+ }
359
+ if ('alpha' in after) {
360
+ this.updateAlpha();
361
+ return;
362
+ }
363
+ if ('fillStyle' in after) {
364
+ this._applyPadColor();
365
+ // fillStyle 변경 시 strokeStyle 미설정이면 outline default 도 같이 갱신
366
+ if (this.component.state.strokeStyle == null)
367
+ this._applyStrokeColor();
368
+ return;
369
+ }
370
+ if ('strokeStyle' in after) {
371
+ this._applyStrokeColor();
372
+ return;
373
+ }
374
+ const rebuildKeys = [
375
+ 'data',
376
+ 'stackPattern', 'carrierPreset',
377
+ 'carrierWidth', 'carrierHeight', 'carrierDepth', 'carrierGap',
378
+ 'capacity', 'stackHeightLimit', 'pickPolicy'
379
+ ];
380
+ if (rebuildKeys.some(k => k in after)) {
381
+ this._rebuildCarriers();
382
+ return;
383
+ }
384
+ super.onchange?.(after, before);
385
+ }
386
+ }
387
+ //# sourceMappingURL=stockpile-3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stockpile-3d.js","sourceRoot":"","sources":["../src/stockpile-3d.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAKxD,MAAM,SAAS,GAAG,CAAC,CAAA;AAEnB,gFAAgF;AAChF,MAAM,YAAY,GAAkC;IAClD,GAAG,EAAE,QAAQ,EAAO,cAAc;IAClC,MAAM,EAAE,QAAQ,EAAI,QAAQ;IAC5B,IAAI,EAAE,QAAQ,EAAM,eAAe;IACnC,IAAI,EAAE,QAAQ,EAAM,UAAU;IAC9B,KAAK,EAAE,QAAQ,EAAK,QAAQ;IAC5B,IAAI,EAAE,QAAQ,CAAM,QAAQ;CAC7B,CAAA;AAED,MAAM,mBAAmB,GAA+D;IACtF,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;IAC5B,MAAM,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;IAC/B,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;IAC7B,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;IAC7B,KAAK,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;IAC9B,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE;CAC9B,CAAA;AAED,MAAM,OAAO,WAAY,SAAQ,eAAe;IACtC,cAAc,CAAc;IAC5B,QAAQ,CAAa;IAC7B,8DAA8D;IACtD,WAAW,CAAqB;IACxC,wEAAwE;IAChE,eAAe,GAA4B,IAAI,GAAG,EAAE,CAAA;IAC5D,6EAA6E;IACrE,gBAAgB,GAA4C,IAAI,GAAG,EAAE,CAAA;IAErE,oBAAoB,CAAC,QAAgB;QAC3C,IAAI,CAAC,GAAG,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC3C,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG,IAAI,KAAK,CAAC,oBAAoB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,CAAA;YACvE,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;QACxC,CAAC;QACD,OAAO,CAAC,CAAA;IACV,CAAC;IAEO,qBAAqB;QAC3B,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE;YAAE,CAAC,CAAC,OAAO,EAAE,CAAA;QAC3D,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAA;IAC/B,CAAC;IAED,KAAK;QACH,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAY,CAAA;QACzC,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,IAAI,GAAG,CAAA;QAC5B,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,CAAA;QAE7B,+DAA+D;QAC/D,MAAM,OAAO,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;QACtD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,CAAA;QACjC,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,oBAAoB,CAAC;YAC5C,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI;YAClE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,IAAI;SAC5C,CAAC,CAAA;QACF,IAAI,CAAC,QAAQ,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC/C,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,SAAS,GAAG,CAAC,CAAA;QACxC,IAAI,CAAC,QAAQ,CAAC,aAAa,GAAG,IAAI,CAAA;QAClC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAEhC,4DAA4D;QAC5D,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE,CAAA;QACxC,MAAM,UAAU,GAAG,IAAI,KAAK,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAA;QACvE,IAAI,CAAC,WAAW,GAAG,IAAI,KAAK,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAA;QACvF,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,GAAG,SAAS,GAAG,CAAC,CAAA;QAC3C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAEnC,6DAA6D;QAC7D,IAAI,CAAC,cAAc,GAAG,IAAI,KAAK,CAAC,KAAK,EAAE,CAAA;QACvC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,GAAG,SAAS,CAAA;QAC1C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAA;QACtC,IAAI,CAAC,gBAAgB,EAAE,CAAA;IACzB,CAAC;IAED,MAAM;QACJ,KAAK,CAAC,MAAM,EAAE,CAAA;QACd,IAAI,CAAC,gBAAgB,EAAE,CAAA;IACzB,CAAC;IAED;;;;;OAKG;IACH,cAAc,CAAC,MAAe;QAC5B,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;YAC7C,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAA;QACvB,CAAC;QACD,OAAO,IAAI,CAAC,QAAQ,CAAA;IACtB,CAAC;IAED;oEACgE;IACxD,SAAS;QACf,MAAM,GAAG,GAAI,IAAI,CAAC,SAAS,CAAC,KAAa,EAAE,SAAS,CAAA;QACpD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC;gBAAC,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;IAClC,CAAC;IAED;uBACmB;IACX,YAAY;QAClB,MAAM,GAAG,GAAI,IAAI,CAAC,SAAS,CAAC,KAAa,EAAE,WAAW,CAAA;QACtD,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC;gBAAC,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;QACjE,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,CAAA;IACrD,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAM;QAC7B,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,QAAmC,CAAA;QAC9D,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAA;QACjC,CAAC,CAAC,WAAW,GAAG,IAAI,CAAA;IACtB,CAAC;IAED,qEAAqE;IAC7D,cAAc;QACpB,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAM;QAC1B,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAsC,CAAA;QAC9D,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,EAAE,CAAA;QAC1B,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACf,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QAClB,CAAC,CAAC,WAAW,GAAG,IAAI,CAAA;IACtB,CAAC;IAEO,gBAAgB;QACtB,MAAM,CAAC,GAAG,IAAI,CAAC,cAAc,CAAA;QAC7B,IAAI,CAAC,CAAC;YAAE,OAAM;QACd,sDAAsD;QACtD,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;QAErD,MAAM,SAAS,GAAG,IAAI,CAAC,SAAiC,CAAA;QACxD,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,CAAA;QACtC,IAAI,KAAK,KAAK,CAAC;YAAE,OAAM;QAEvB,MAAM,KAAK,GAAG,SAAS,CAAC,KAAY,CAAA;QACpC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,GAAG,CAAA;QAChC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,CAAA;QACjC,MAAM,OAAO,GAAI,KAAK,CAAC,YAA6B,IAAI,KAAK,CAAA;QAC7D,MAAM,MAAM,GAAI,KAAK,CAAC,aAA+B,IAAI,KAAK,CAAA;QAE9D,0CAA0C;QAC1C,MAAM,GAAG,GAAG,mBAAmB,CAAC,MAAM,CAAC,IAAI,mBAAmB,CAAC,GAAG,CAAA;QAClE,MAAM,EAAE,GAAG,KAAK,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,CAAA;QACtC,MAAM,EAAE,GAAG,KAAK,CAAC,aAAa,IAAI,GAAG,CAAC,CAAC,CAAA;QACvC,MAAM,EAAE,GAAG,KAAK,CAAC,YAAY,IAAI,GAAG,CAAC,CAAC,CAAA;QACtC,MAAM,GAAG,GAAG,KAAK,CAAC,UAAU,IAAI,EAAE,CAAA,CAAG,kEAAkE;QAEvG,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;QACtD,2DAA2D;QAC3D,oDAAoD;QACpD,MAAM,kBAAkB,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,GAAG,CAAA;QACnE,IAAI,CAAC,qBAAqB,EAAE,CAAA;QAC5B,MAAM,YAAY,GAAG,CAAC,MAAW,EAAU,EAAE;YAC3C,MAAM,WAAW,GAAI,SAAiB,CAAC,kBAAkB,EAAE,CAAC,MAAM,CAAC,CAAA;YACnE,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9D,IAAI,CAAC;oBAAC,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,CAAA;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;YAClF,CAAC;YACD,OAAO,kBAAkB,CAAA;QAC3B,CAAC,CAAA;QAED,MAAM,SAAS,GAAG,IAAI,CAAC,sBAAsB,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,gBAAgB,CAAC,CAAA;QACpH,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAA;QAC5B,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAA;QACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC1C,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;YACtB,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;YACzB,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,CAAA;YACrC,MAAM,GAAG,GAAG,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAA;YAC/C,MAAM,IAAI,GAAG,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;YACtC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;YAChC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;YACtB,IAAI,CAAC,aAAa,GAAG,IAAI,CAAA;YACzB,+DAA+D;YAC/D,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;YAC1B,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;gBAChB,IAAI,CAAC,QAAQ,CAAC,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;gBACpC,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;YAC7C,CAAC;YACD,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACb,CAAC;IACH,CAAC;IAED,iEAAiE;IACjE,iBAAiB,CAAC,QAAgB;QAChC,OAAO,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC3C,CAAC;IAED;;;OAGG;IACK,sBAAsB,CAC5B,OAAqB,EACrB,KAAa,EACb,KAAa,EACb,KAAa,EACb,EAAU,EACV,EAAU,EACV,EAAU,EACV,GAAW,EACX,WAAoB;QAEpB,MAAM,GAAG,GAA+C,EAAE,CAAA;QAE1D,uBAAuB;QACvB,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,WAAW,IAAI,KAAK,CAAA;YACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,CAAA;gBACrC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;YAClD,CAAC;YACD,OAAO,GAAG,CAAA;QACZ,CAAC;QAED,mEAAmE;QACnE,MAAM,MAAM,GAAG,EAAE,GAAG,GAAG,CAAA;QACvB,MAAM,MAAM,GAAG,EAAE,GAAG,GAAG,CAAA;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAA;QAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,CAAA;QAC5D,MAAM,aAAa,GAAG,IAAI,GAAG,IAAI,CAAA;QAEjC,mCAAmC;QACnC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,IAAI,SAAS,GAAG,KAAK,CAAA;YACrB,IAAI,KAAK,GAAG,CAAC,CAAA;YACb,OAAO,SAAS,GAAG,CAAC,EAAE,CAAC;gBACrB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,KAAK,CAAC,CAAA;gBACvC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,KAAK,CAAC,CAAA;gBACvC,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,CAAA;gBACpC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;gBAChD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC9B,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;oBACnB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,KAAK,CAAC,CAAA;oBAC/B,yBAAyB;oBACzB,MAAM,SAAS,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;oBACpC,MAAM,SAAS,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;oBACpC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,GAAG,GAAG,CAAC,GAAG,MAAM,CAAA;oBACrD,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,SAAS,GAAG,GAAG,CAAC,GAAG,MAAM,CAAA;oBACrD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;gBAC5C,CAAC;gBACD,SAAS,IAAI,IAAI,CAAA;gBACjB,KAAK,EAAE,CAAA;gBACP,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;oBAC/B,6BAA6B;oBAC7B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;wBACnC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;oBACxD,CAAC;oBACD,MAAK;gBACP,CAAC;gBACD,IAAI,WAAW,IAAI,KAAK,IAAI,WAAW;oBAAE,MAAK;YAChD,CAAC;YACD,OAAO,GAAG,CAAA;QACZ,CAAC;QAED,gDAAgD;QAChD,kCAAkC;QAClC,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;YACvB,wCAAwC;YACxC,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAA;YAC7E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,aAAa,CAAC,CAAA;gBAC3C,MAAM,GAAG,GAAG,CAAC,GAAG,aAAa,CAAA;gBAC7B,MAAM,CAAC,GAAG,GAAG,GAAG,IAAI,CAAA;gBACpB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAA;gBAChC,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,CAAA;gBAC7C,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,CAAA;gBACjD,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,MAAM,GAAG,OAAO,CAAA;gBACnD,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,MAAM,GAAG,OAAO,CAAA;gBACnD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;gBAC1C,IAAI,WAAW,IAAI,KAAK,IAAI,WAAW,GAAG,CAAC,IAAI,GAAG,KAAK,aAAa,GAAG,CAAC;oBAAE,MAAK;YACjF,CAAC;YACD,OAAO,GAAG,CAAA;QACZ,CAAC;QAED,oCAAoC;QACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,aAAa,CAAC,CAAA;YAC3C,IAAI,WAAW,IAAI,KAAK,IAAI,WAAW;gBAAE,MAAK;YAC9C,MAAM,GAAG,GAAG,CAAC,GAAG,aAAa,CAAA;YAC7B,MAAM,CAAC,GAAG,GAAG,GAAG,IAAI,CAAA;YACpB,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAA;YAChC,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,MAAM,CAAA;YACvC,MAAM,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,MAAM,CAAA;YACzC,+CAA+C;YAC/C,IAAI,OAAO,KAAK,WAAW,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC/C,CAAC,IAAI,MAAM,GAAG,CAAC,CAAA;YACjB,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;QAC5C,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IAED,2DAA2D;IACnD,gBAAgB,CAAC,MAAqB,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS;QAC7E,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACtB,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAA;YACjC,OAAO,IAAI,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;QAC1D,CAAC;QACD,+BAA+B;QAC/B,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YAC3C,OAAO,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,CAAA;QAC5D,CAAC;QACD,OAAO,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACvC,CAAC;IAED;;;;OAIG;IACH,WAAW;QACT,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAY,CAAA;QACzC,MAAM,KAAK,GAAG,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAC/D,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAsC,CAAA;YAC9D,CAAC,CAAC,OAAO,GAAG,IAAI,GAAG,KAAK,CAAA;YACxB,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,OAAO,GAAG,CAAC,CAAA;YAC7B,CAAC,CAAC,WAAW,GAAG,IAAI,CAAA;QACtB,CAAC;QACD,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,cAAc,CAAC,QAAQ,EAAE,CAAC;gBACjD,MAAM,IAAI,GAAG,KAAmB,CAAA;gBAChC,MAAM,CAAC,GAAG,IAAI,CAAC,QAAsC,CAAA;gBACrD,IAAI,CAAC,CAAC;oBAAE,SAAQ;gBAChB,CAAC,CAAC,OAAO,GAAG,KAAK,CAAA;gBACjB,CAAC,CAAC,WAAW,GAAG,KAAK,GAAG,CAAC,CAAA;gBACzB,CAAC,CAAC,WAAW,GAAG,IAAI,CAAA;YACtB,CAAC;QACH,CAAC;IACH,CAAC;IAED,eAAe;QACb,0DAA0D;QAC1D,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAY,CAAA;YACzC,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,IAAI,GAAG,CAAA;YAC5B,MAAM,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,GAAG,CAAA;YAC7B,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAA;YAChC,IAAI,CAAC,QAAQ,CAAC,QAAQ,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;YAC/D,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBACrB,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAA;gBACnC,IAAI,CAAC,WAAW,CAAC,QAAQ,GAAG,IAAI,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;YAC7E,CAAC;QACH,CAAC;QACD,IAAI,CAAC,gBAAgB,EAAE,CAAA;IACzB,CAAC;IAED;;;;OAIG;IACH,QAAQ,CAAC,KAA8B,EAAE,MAA+B;QACtE,IAAI,OAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,EAAE,CAAC;YAC1C,IAAI,CAAC,eAAe,EAAE,CAAA;YACtB,OAAM;QACR,CAAC;QACD,IAAI,OAAO,IAAI,KAAK,EAAE,CAAC;YACrB,IAAI,CAAC,WAAW,EAAE,CAAA;YAClB,OAAM;QACR,CAAC;QACD,IAAI,WAAW,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,CAAC,cAAc,EAAE,CAAA;YACrB,2DAA2D;YAC3D,IAAK,IAAI,CAAC,SAAS,CAAC,KAAa,CAAC,WAAW,IAAI,IAAI;gBAAE,IAAI,CAAC,iBAAiB,EAAE,CAAA;YAC/E,OAAM;QACR,CAAC;QACD,IAAI,aAAa,IAAI,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,iBAAiB,EAAE,CAAA;YACxB,OAAM;QACR,CAAC;QACD,MAAM,WAAW,GAAG;YAClB,MAAM;YACN,cAAc,EAAE,eAAe;YAC/B,cAAc,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY;YAC7D,UAAU,EAAE,kBAAkB,EAAE,YAAY;SAC7C,CAAA;QACD,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACtC,IAAI,CAAC,gBAAgB,EAAE,CAAA;YACvB,OAAM;QACR,CAAC;QACD,KAAK,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAA;IACjC,CAAC;CACF","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Stockpile3D — 평치 영역의 3D 시각화.\n *\n * - floor pad (얇은 box, 영역 footprint).\n * - records.length 만큼 가상 carrier mesh 를 *_stackPattern_* 으로 자동 배치.\n * - mesh geometry / material 은 *_carrierPreset_* 별.\n *\n * 핵심 — 실제 carrier 컴포넌트를 자식으로 두지 않고 *_가상 mesh_* 만 그려서 다수\n * 적치를 가볍게 표현. records 변경 시 update() 가 호출되어 mesh 가 재배치된다.\n */\n\nimport * as THREE from 'three'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nimport type Stockpile from './stockpile.js'\nimport type { StackPattern, CarrierPreset } from './stockpile.js'\n\nconst PAD_DEPTH = 2\n\n/** carrierPreset 별 default 색 (state.carrierWidth/Height/Depth 미지정 시 크기와 함께). */\nconst PRESET_COLOR: Record<CarrierPreset, number> = {\n box: 0xb87333, // kraft brown\n pallet: 0x8b6f47, // 짙은 나무\n drum: 0x556679, // 금속/blue-grey\n sack: 0xd4c89a, // beige 천\n crate: 0x9a7548, // 나무 상자\n bale: 0xa89968 // 압축 베일\n}\n\nconst PRESET_DEFAULT_SIZE: Record<CarrierPreset, { w: number; h: number; d: number }> = {\n box: { w: 26, h: 26, d: 20 },\n pallet: { w: 40, h: 30, d: 15 },\n drum: { w: 24, h: 24, d: 30 },\n sack: { w: 30, h: 28, d: 18 },\n crate: { w: 30, h: 30, d: 22 },\n bale: { w: 32, h: 30, d: 18 }\n}\n\nexport class Stockpile3D extends RealObjectGroup {\n private _carriersGroup?: THREE.Group\n private _padMesh?: THREE.Mesh\n /** pad 의 외곽선 — strokeStyle 표현 (2D dashed outline 의 3D 대응). */\n private _padOutline?: THREE.LineSegments\n /** record.id → 해당 carrier mesh 매핑 — popup anchor / tether 의 대상으로 사용. */\n private _meshByRecordId: Map<string, THREE.Mesh> = new Map()\n /** Legend 매핑된 색을 material 로 캐시 — 같은 색 record 들은 material 공유 (drawcall ↓). */\n private _materialByColor: Map<number, THREE.MeshStandardMaterial> = new Map()\n\n private _getMaterialForColor(colorHex: number): THREE.MeshStandardMaterial {\n let m = this._materialByColor.get(colorHex)\n if (!m) {\n m = new THREE.MeshStandardMaterial({ color: colorHex, roughness: 0.7 })\n this._materialByColor.set(colorHex, m)\n }\n return m\n }\n\n private _disposeMaterialCache(): void {\n for (const m of this._materialByColor.values()) m.dispose()\n this._materialByColor.clear()\n }\n\n build() {\n super.build()\n const state = this.component.state as any\n const w = state.width ?? 100\n const h = state.height ?? 100\n\n // ── floor pad — 영역 표시 (얇은 box) ──────────────────────────────\n const padGeom = new THREE.BoxGeometry(w, PAD_DEPTH, h)\n const padColor = this._padColor()\n const padMat = new THREE.MeshStandardMaterial({\n color: padColor, roughness: 0.85, transparent: true, opacity: 0.55,\n emissive: padColor, emissiveIntensity: 0.05\n })\n this._padMesh = new THREE.Mesh(padGeom, padMat)\n this._padMesh.position.y = PAD_DEPTH / 2\n this._padMesh.receiveShadow = true\n this.object3d.add(this._padMesh)\n\n // pad 외곽선 — strokeStyle 3D 표현. 2D 의 dashed outline 과 같은 의도.\n const outlineColor = this._strokeColor()\n const outlineMat = new THREE.LineBasicMaterial({ color: outlineColor })\n this._padOutline = new THREE.LineSegments(new THREE.EdgesGeometry(padGeom), outlineMat)\n this._padOutline.position.y = PAD_DEPTH / 2\n this.object3d.add(this._padOutline)\n\n // ── carriers group — records 기반 자동 적치 ─────────────────────\n this._carriersGroup = new THREE.Group()\n this._carriersGroup.position.y = PAD_DEPTH\n this.object3d.add(this._carriersGroup)\n this._rebuildCarriers()\n }\n\n update() {\n super.update()\n this._rebuildCarriers()\n }\n\n /**\n * carrier attach point.\n * - slotId 가 record.id 와 매칭되면 그 record 의 mesh 반환 → popup tether 가 정확히\n * 해당 stock 에 연결.\n * - 아니면 pad 반환 (단일 'pile' slot, mover place 의 default anchor).\n */\n getAttachFrame(slotId?: string): THREE.Object3D | undefined {\n if (slotId) {\n const mesh = this._meshByRecordId.get(slotId)\n if (mesh) return mesh\n }\n return this._padMesh\n }\n\n /** state.fillStyle → THREE.Color. 잘못된 값/없으면 default. capacity 점유율에 따른\n * 색 변경은 강요하지 않음 — 필요하면 사용자가 mapping(eval)으로 fillStyle 을 조정. */\n private _padColor(): THREE.Color {\n const raw = (this.component.state as any)?.fillStyle\n if (typeof raw === 'string' && raw.length > 0) {\n try { return new THREE.Color(raw) } catch { /* fallthrough */ }\n }\n return new THREE.Color(0xc89c5c)\n }\n\n /** state.strokeStyle → THREE.Color (pad 외곽선 색). 미설정 시 fillStyle 따라가되\n * 대비 위해 약간 어둡게 */\n private _strokeColor(): THREE.Color {\n const raw = (this.component.state as any)?.strokeStyle\n if (typeof raw === 'string' && raw.length > 0) {\n try { return new THREE.Color(raw) } catch { /* fallthrough */ }\n }\n return this._padColor().clone().multiplyScalar(0.6)\n }\n\n private _applyStrokeColor(): void {\n if (!this._padOutline) return\n const m = this._padOutline.material as THREE.LineBasicMaterial\n m.color.copy(this._strokeColor())\n m.needsUpdate = true\n }\n\n /** state.fillStyle 변경 즉시 반영 — pad material 의 color + emissive 갱신. */\n private _applyPadColor(): void {\n if (!this._padMesh) return\n const m = this._padMesh.material as THREE.MeshStandardMaterial\n const c = this._padColor()\n m.color.copy(c)\n m.emissive.copy(c)\n m.needsUpdate = true\n }\n\n private _rebuildCarriers(): void {\n const g = this._carriersGroup\n if (!g) return\n // 기존 mesh 모두 제거 (geometry/material 은 공유돼 dispose 안 함)\n while (g.children.length > 0) g.remove(g.children[0])\n\n const stockpile = this.component as unknown as Stockpile\n const count = stockpile.records.length\n if (count === 0) return\n\n const state = stockpile.state as any\n const areaW = state.width ?? 100\n const areaH = state.height ?? 100\n const pattern = (state.stackPattern as StackPattern) ?? 'row'\n const preset = (state.carrierPreset as CarrierPreset) ?? 'box'\n\n // carrier 크기 — 명시값 우선, 없으면 preset default\n const def = PRESET_DEFAULT_SIZE[preset] ?? PRESET_DEFAULT_SIZE.box\n const cw = state.carrierWidth ?? def.w\n const ch = state.carrierHeight ?? def.h\n const cd = state.carrierDepth ?? def.d\n const gap = state.carrierGap ?? 10 // 평면(xz) cell 간격 — 0 이면 딱 붙음. carrier 크기 대비 충분히 분리되도록 default 10.\n\n const geom = this._carrierGeometry(preset, cw, ch, cd)\n // record 별로 색이 다를 수 있어(Legend 매핑) material 을 record 별로 생성.\n // 같은 색은 material 캐시로 공유해 dispose 비용 / drawcall 최적화.\n const presetDefaultColor = PRESET_COLOR[preset] ?? PRESET_COLOR.box\n this._disposeMaterialCache()\n const resolveColor = (record: any): number => {\n const legendColor = (stockpile as any).resolveLegendColor?.(record)\n if (typeof legendColor === 'string' && legendColor.length > 0) {\n try { return new THREE.Color(legendColor).getHex() } catch { /* fallthrough */ }\n }\n return presetDefaultColor\n }\n\n const positions = this._computeStackPositions(pattern, count, areaW, areaH, cw, ch, cd, gap, state.stackHeightLimit)\n this._meshByRecordId.clear()\n const records = stockpile.records\n for (let i = 0; i < positions.length; i++) {\n const p = positions[i]\n const record = records[i]\n const colorHex = resolveColor(record)\n const mat = this._getMaterialForColor(colorHex)\n const mesh = new THREE.Mesh(geom, mat)\n mesh.position.set(p.x, p.y, p.z)\n mesh.castShadow = true\n mesh.receiveShadow = true\n // raycast hit → record 식별을 위해 userData 에 recordId 저장 + Map 갱신.\n const rid = records[i]?.id\n if (rid != null) {\n mesh.userData.recordId = String(rid)\n this._meshByRecordId.set(String(rid), mesh)\n }\n g.add(mesh)\n }\n }\n\n /** record.id 로 해당 carrier mesh 직접 조회 — popup tether anchor 용. */\n getMeshByRecordId(recordId: string): THREE.Mesh | undefined {\n return this._meshByRecordId.get(recordId)\n }\n\n /**\n * stackPattern 별 각 carrier 의 local 좌표(영역 중심 기준). y 는 pad 위에 쌓이는\n * 높이(cd 단위), x/z 는 영역 평면 위 분포.\n */\n private _computeStackPositions(\n pattern: StackPattern,\n count: number,\n areaW: number,\n areaH: number,\n cw: number,\n ch: number,\n cd: number,\n gap: number,\n heightLimit?: number\n ): Array<{ x: number; y: number; z: number }> {\n const out: Array<{ x: number; y: number; z: number }> = []\n\n // column — 한 자리에 수직 적층\n if (pattern === 'column') {\n const layers = heightLimit ?? count\n for (let i = 0; i < count; i++) {\n const layer = Math.min(i, layers - 1)\n out.push({ x: 0, y: layer * cd + cd / 2, z: 0 })\n }\n return out\n }\n\n // 격자 cell — 행/열 수. cell stride = carrier + gap (인접 carrier 사이 분리).\n const stridX = cw + gap\n const stridZ = ch + gap\n const cols = Math.max(1, Math.floor((areaW + gap) / stridX))\n const rows = Math.max(1, Math.floor((areaH + gap) / stridZ))\n const cellsPerLayer = cols * rows\n\n // pyramid — 위로 갈수록 행/열 -1 씩 (사다리꼴)\n if (pattern === 'pyramid') {\n let remaining = count\n let layer = 0\n while (remaining > 0) {\n const lcols = Math.max(1, cols - layer)\n const lrows = Math.max(1, rows - layer)\n const cellsThisLayer = lcols * lrows\n const take = Math.min(remaining, cellsThisLayer)\n for (let i = 0; i < take; i++) {\n const c = i % lcols\n const r = Math.floor(i / lcols)\n // 가운데 정렬 — 줄어든 만큼 offset\n const offsetCol = (cols - lcols) / 2\n const offsetRow = (rows - lrows) / 2\n const x = -areaW / 2 + (c + offsetCol + 0.5) * stridX\n const z = -areaH / 2 + (r + offsetRow + 0.5) * stridZ\n out.push({ x, y: layer * cd + cd / 2, z })\n }\n remaining -= take\n layer++\n if (lcols === 1 && lrows === 1) {\n // 정상에 도달 — 남은 건 column 처럼 위로\n for (let i = 0; i < remaining; i++) {\n out.push({ x: 0, y: (layer + i) * cd + cd / 2, z: 0 })\n }\n break\n }\n if (heightLimit && layer >= heightLimit) break\n }\n return out\n }\n\n // pile — 자연 더미. row 와 비슷하되 가장자리는 약간 옆으로 흘러내림 효과\n // (간단 구현 — 단마다 약간 random offset).\n if (pattern === 'pile') {\n // pseudo-random (인덱스 기반, deterministic)\n const rng = (i: number) => ((Math.sin(i * 12.9898) * 43758.5453) % 1 + 1) % 1\n for (let i = 0; i < count; i++) {\n const layer = Math.floor(i / cellsPerLayer)\n const idx = i % cellsPerLayer\n const c = idx % cols\n const r = Math.floor(idx / cols)\n const jitterX = (rng(i * 2) - 0.5) * cw * 0.2\n const jitterZ = (rng(i * 2 + 1) - 0.5) * ch * 0.2\n const x = -areaW / 2 + (c + 0.5) * stridX + jitterX\n const z = -areaH / 2 + (r + 0.5) * stridZ + jitterZ\n out.push({ x, y: layer * cd + cd / 2, z })\n if (heightLimit && layer >= heightLimit - 1 && idx === cellsPerLayer - 1) break\n }\n return out\n }\n\n // row (default) / staggered — 격자 적층\n for (let i = 0; i < count; i++) {\n const layer = Math.floor(i / cellsPerLayer)\n if (heightLimit && layer >= heightLimit) break\n const idx = i % cellsPerLayer\n const c = idx % cols\n const r = Math.floor(idx / cols)\n let x = -areaW / 2 + (c + 0.5) * stridX\n const z = -areaH / 2 + (r + 0.5) * stridZ\n // staggered — 홀수 layer 는 1/2 cell offset (벽돌식)\n if (pattern === 'staggered' && layer % 2 === 1) {\n x += stridX / 2\n }\n out.push({ x, y: layer * cd + cd / 2, z })\n }\n return out\n }\n\n /** carrierPreset 별 geometry. drum 은 cylinder, 나머지는 box. */\n private _carrierGeometry(preset: CarrierPreset, w: number, h: number, d: number): THREE.BufferGeometry {\n if (preset === 'drum') {\n const radius = Math.min(w, h) / 2\n return new THREE.CylinderGeometry(radius, radius, d, 16)\n }\n // sack/bale — 약간 작게 (자연스러운 형태)\n if (preset === 'sack' || preset === 'bale') {\n return new THREE.BoxGeometry(w * 0.92, d * 0.92, h * 0.92)\n }\n return new THREE.BoxGeometry(w, d, h)\n }\n\n /**\n * state.alpha 변경 시 pad + carriers 의 material opacity 즉시 반영.\n * pad 는 원래 0.55 base opacity 위에 alpha 곱 (영역 표시가 너무 진해지지 않도록).\n * carriers 는 alpha 그대로 (단순한 곱은 fully opaque 이 1, 반투명이 0).\n */\n updateAlpha(): void {\n const state = this.component.state as any\n const alpha = typeof state.alpha === 'number' ? state.alpha : 1\n if (this._padMesh) {\n const m = this._padMesh.material as THREE.MeshStandardMaterial\n m.opacity = 0.55 * alpha\n m.transparent = m.opacity < 1\n m.needsUpdate = true\n }\n if (this._carriersGroup) {\n for (const child of this._carriersGroup.children) {\n const mesh = child as THREE.Mesh\n const m = mesh.material as THREE.MeshStandardMaterial\n if (!m) continue\n m.opacity = alpha\n m.transparent = alpha < 1\n m.needsUpdate = true\n }\n }\n }\n\n updateDimension(): void {\n // width/height 변경 시 pad geometry + outline + carriers 재계산\n if (this._padMesh) {\n const state = this.component.state as any\n const w = state.width ?? 100\n const h = state.height ?? 100\n this._padMesh.geometry.dispose()\n this._padMesh.geometry = new THREE.BoxGeometry(w, PAD_DEPTH, h)\n if (this._padOutline) {\n this._padOutline.geometry.dispose()\n this._padOutline.geometry = new THREE.EdgesGeometry(this._padMesh.geometry)\n }\n }\n this._rebuildCarriers()\n }\n\n /**\n * state 변경 시 즉시 3D 갱신 — property panel 의 수정이 바로 반영되도록.\n * - width/height → pad geometry 재생성 + carriers 재계산 (updateDimension)\n * - 적치/외형 관련 키 → carriers 만 재계산 (_rebuildCarriers)\n */\n onchange(after: Record<string, unknown>, before: Record<string, unknown>): void {\n if ('width' in after || 'height' in after) {\n this.updateDimension()\n return\n }\n if ('alpha' in after) {\n this.updateAlpha()\n return\n }\n if ('fillStyle' in after) {\n this._applyPadColor()\n // fillStyle 변경 시 strokeStyle 미설정이면 outline default 도 같이 갱신\n if ((this.component.state as any).strokeStyle == null) this._applyStrokeColor()\n return\n }\n if ('strokeStyle' in after) {\n this._applyStrokeColor()\n return\n }\n const rebuildKeys = [\n 'data',\n 'stackPattern', 'carrierPreset',\n 'carrierWidth', 'carrierHeight', 'carrierDepth', 'carrierGap',\n 'capacity', 'stackHeightLimit', 'pickPolicy'\n ]\n if (rebuildKeys.some(k => k in after)) {\n this._rebuildCarriers()\n return\n }\n super.onchange?.(after, before)\n }\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import * as THREE from 'three';
2
+ import { RealObjectGroup } from '@hatiolab/things-scene';
3
+ export declare class StockpileGrid3D extends RealObjectGroup {
4
+ /** cellSlotId → pad mesh (popup tether anchor 용). */
5
+ private _padByCellId;
6
+ /** cellSlotId → carriers group. */
7
+ private _carriersByCellId;
8
+ /** material 캐시 (color → material) — 같은 색 carrier 공유. */
9
+ private _materialByColor;
10
+ /** grid 전체를 감싸는 outer group (보드 보정 등). */
11
+ private _gridGroup?;
12
+ build(): void;
13
+ update(): void;
14
+ /** carrier attach anchor — slotId 가 cellId 면 그 cell pad 반환, 아니면 grid group. */
15
+ getAttachFrame(slotId?: string): THREE.Object3D | undefined;
16
+ /** slotTargetAt(cellId).attachObject3d = cell pad — Stockpile.getSlotAttachObject3d 가 호출. */
17
+ getCellPadMesh(slotId: string): THREE.Mesh | undefined;
18
+ private _disposeAll;
19
+ private _disposeGroupChildren;
20
+ private _rebuildAll;
21
+ private _buildGrid;
22
+ /** cell 의 records 를 stackPattern / carrierPreset 으로 배치. */
23
+ private _populateCellCarriers;
24
+ private _computeStackPositions;
25
+ private _carrierGeometry;
26
+ private _getMaterial;
27
+ onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
28
+ updateDimension(): void;
29
+ updateAlpha(): void;
30
+ }