@operato/scene-transport 10.0.0-beta.46 → 10.0.0-beta.48
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.
- package/CHANGELOG.md +40 -0
- package/dist/agv.d.ts +23 -2
- package/dist/agv.js +85 -0
- package/dist/agv.js.map +1 -1
- package/dist/amr-3d.d.ts +4 -0
- package/dist/amr-3d.js +148 -0
- package/dist/amr-3d.js.map +1 -0
- package/dist/amr.d.ts +139 -0
- package/dist/amr.js +509 -0
- package/dist/amr.js.map +1 -0
- package/dist/charging-station-3d.d.ts +4 -0
- package/dist/charging-station-3d.js +103 -0
- package/dist/charging-station-3d.js.map +1 -0
- package/dist/charging-station.d.ts +47 -0
- package/dist/charging-station.js +236 -0
- package/dist/charging-station.js.map +1 -0
- package/dist/deadlock.d.ts +39 -0
- package/dist/deadlock.js +164 -0
- package/dist/deadlock.js.map +1 -0
- package/dist/forbidden-zone.d.ts +24 -0
- package/dist/forbidden-zone.js +71 -0
- package/dist/forbidden-zone.js.map +1 -0
- package/dist/forklift.d.ts +20 -2
- package/dist/forklift.js +87 -1
- package/dist/forklift.js.map +1 -1
- package/dist/grid-sweep.d.ts +44 -0
- package/dist/grid-sweep.js +80 -0
- package/dist/grid-sweep.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -1
- package/dist/intersection-3d.d.ts +4 -0
- package/dist/intersection-3d.js +49 -0
- package/dist/intersection-3d.js.map +1 -0
- package/dist/intersection.d.ts +35 -0
- package/dist/intersection.js +91 -0
- package/dist/intersection.js.map +1 -0
- package/dist/lane-3d.d.ts +4 -0
- package/dist/lane-3d.js +101 -0
- package/dist/lane-3d.js.map +1 -0
- package/dist/lane.d.ts +50 -0
- package/dist/lane.js +106 -0
- package/dist/lane.js.map +1 -0
- package/dist/nav-graph-from-components.d.ts +64 -0
- package/dist/nav-graph-from-components.js +73 -0
- package/dist/nav-graph-from-components.js.map +1 -0
- package/dist/nav-graph-from-facility.d.ts +11 -0
- package/dist/nav-graph-from-facility.js +248 -0
- package/dist/nav-graph-from-facility.js.map +1 -0
- package/dist/nav-graph-from-grid.d.ts +39 -0
- package/dist/nav-graph-from-grid.js +166 -0
- package/dist/nav-graph-from-grid.js.map +1 -0
- package/dist/nav-zone.d.ts +34 -0
- package/dist/nav-zone.js +74 -0
- package/dist/nav-zone.js.map +1 -0
- package/dist/navigable-helpers.d.ts +85 -0
- package/dist/navigable-helpers.js +302 -0
- package/dist/navigable-helpers.js.map +1 -0
- package/dist/phase-utils.d.ts +21 -0
- package/dist/phase-utils.js +37 -0
- package/dist/phase-utils.js.map +1 -0
- package/dist/polygon-utils.d.ts +71 -0
- package/dist/polygon-utils.js +159 -0
- package/dist/polygon-utils.js.map +1 -0
- package/dist/replan.d.ts +73 -0
- package/dist/replan.js +138 -0
- package/dist/replan.js.map +1 -0
- package/dist/speed-zone.d.ts +35 -0
- package/dist/speed-zone.js +97 -0
- package/dist/speed-zone.js.map +1 -0
- package/dist/templates/index.d.ts +518 -0
- package/dist/templates/index.js +150 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/traffic-controller-3d.d.ts +4 -0
- package/dist/traffic-controller-3d.js +60 -0
- package/dist/traffic-controller-3d.js.map +1 -0
- package/dist/traffic-controller.d.ts +37 -0
- package/dist/traffic-controller.js +124 -0
- package/dist/traffic-controller.js.map +1 -0
- package/dist/tugger.d.ts +20 -2
- package/dist/tugger.js +81 -0
- package/dist/tugger.js.map +1 -1
- package/dist/vertical-edges.d.ts +70 -0
- package/dist/vertical-edges.js +118 -0
- package/dist/vertical-edges.js.map +1 -0
- package/dist/waypoint-3d.d.ts +4 -0
- package/dist/waypoint-3d.js +54 -0
- package/dist/waypoint-3d.js.map +1 -0
- package/dist/waypoint.d.ts +24 -0
- package/dist/waypoint.js +64 -0
- package/dist/waypoint.js.map +1 -0
- package/dist/zone-plate-3d.d.ts +7 -0
- package/dist/zone-plate-3d.js +75 -0
- package/dist/zone-plate-3d.js.map +1 -0
- package/package.json +4 -4
- package/src/agv.ts +109 -2
- package/src/amr-3d.ts +160 -0
- package/src/amr.ts +631 -0
- package/src/charging-station-3d.ts +111 -0
- package/src/charging-station.ts +296 -0
- package/src/deadlock.ts +191 -0
- package/src/forbidden-zone.ts +86 -0
- package/src/forklift.ts +102 -3
- package/src/grid-sweep.ts +107 -0
- package/src/index.ts +78 -0
- package/src/intersection-3d.ts +55 -0
- package/src/intersection.ts +111 -0
- package/src/lane-3d.ts +105 -0
- package/src/lane.ts +137 -0
- package/src/nav-graph-from-components.ts +111 -0
- package/src/nav-graph-from-facility.ts +278 -0
- package/src/nav-graph-from-grid.ts +196 -0
- package/src/nav-zone.ts +89 -0
- package/src/navigable-helpers.ts +320 -0
- package/src/phase-utils.ts +45 -0
- package/src/polygon-utils.ts +193 -0
- package/src/replan.ts +193 -0
- package/src/speed-zone.ts +117 -0
- package/src/templates/index.ts +150 -0
- package/src/traffic-controller-3d.ts +67 -0
- package/src/traffic-controller.ts +149 -0
- package/src/tugger.ts +97 -2
- package/src/vertical-edges.ts +164 -0
- package/src/waypoint-3d.ts +61 -0
- package/src/waypoint.ts +82 -0
- package/src/zone-plate-3d.ts +81 -0
- package/test/setup.js +279 -0
- package/test/test-amr-logic.ts +298 -0
- package/test/test-charging-station-logic.ts +188 -0
- package/test/test-deadlock.ts +258 -0
- package/test/test-multi-carrier-slots.ts +163 -0
- package/test/test-nav-components.ts +313 -0
- package/test/test-nav-graph-from-facility.ts +204 -0
- package/test/test-nav-graph-from-grid.ts +199 -0
- package/test/test-navigable-helpers.ts +210 -0
- package/test/test-polygon-sat.ts +218 -0
- package/test/test-replan.ts +260 -0
- package/test/test-traffic-controller.ts +211 -0
- package/test/test-vertical-edges.ts +322 -0
- package/test/test-zones.ts +201 -0
- package/translations/en.json +33 -1
- package/translations/ja.json +33 -1
- package/translations/ko.json +33 -1
- package/translations/ms.json +33 -1
- package/translations/zh.json +33 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,46 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [10.0.0-beta.48](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.47...v10.0.0-beta.48) (2026-05-26)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### :bug: Bug Fix
|
|
10
|
+
|
|
11
|
+
* **scene-base/transport): nature getter 패턴 통일 + feat(facility:** Room alpha (투명도) ([9685bbe](https://github.com/things-scene/operato-scene/commit/9685bbe8efe588fc5b817116f76c73973b3ab063))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## [10.0.0-beta.47](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.46...v10.0.0-beta.47) (2026-05-26)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### :rocket: New Features
|
|
19
|
+
|
|
20
|
+
* **i18n:** 신규 컴포넌트 + label 다국어 처리 (ko/en/ja/zh/ms × 3 packages) ([bed24ce](https://github.com/things-scene/operato-scene/commit/bed24cef4c63b704976b7fc9f13571916175c2b3))
|
|
21
|
+
* **transport/storage:** Phase Z PR-4 — multi-carrier mover (forkSlots state) ([5e398d1](https://github.com/things-scene/operato-scene/commit/5e398d12075c7732d42a27a439d2246acb735289))
|
|
22
|
+
* **transport:** AGV / Forklift / Tugger Navigable 통합 + helpers 추출 ([eaa951f](https://github.com/things-scene/operato-scene/commit/eaa951f79bce35ac7fdba2bd8dff17b26de77c49))
|
|
23
|
+
* **transport:** AMR-1/AMR-2 — ChargingStation + AMR base (Mover + Navigable) ([eed554c](https://github.com/things-scene/operato-scene/commit/eed554c5660879112b33fd81c993eafbafe1c349))
|
|
24
|
+
* **transport:** AMR-3/5/6/7 — followReservation 정밀화 + battery + idle + NavGraph 자동 ([1a93f68](https://github.com/things-scene/operato-scene/commit/1a93f68330675c39e11521369fae448ad8cba200))
|
|
25
|
+
* **transport:** AN-PR-2.1 — Grid-based NavGraph 자동 구성 (obstacle scan) ([256dec6](https://github.com/things-scene/operato-scene/commit/256dec6498646535cbb23b0678ea01bc3ed0e05e))
|
|
26
|
+
* **transport:** AN-PR-4 — Lane / Waypoint / Intersection visual 컴포넌트 ([595cabc](https://github.com/things-scene/operato-scene/commit/595cabca7dae6414ff37029e2e81e0b6c61ee4e4))
|
|
27
|
+
* **transport:** AN-PR-5 — ForbiddenZone / NavZone / SpeedZone + polygon utils ([bb695df](https://github.com/things-scene/operato-scene/commit/bb695df1c40fed234fc0a9cfb42a9988c42ba2cc))
|
|
28
|
+
* **transport:** AN-PR-6 — TrafficController + SpeedZone wiring (AMR 적용) ([6305c4d](https://github.com/things-scene/operato-scene/commit/6305c4d34379162af4b4069229e2aea86037e0d7))
|
|
29
|
+
* **transport:** Auto-Nav 정교화 — deadlock detection ([c0d7d87](https://github.com/things-scene/operato-scene/commit/c0d7d87e6ca9119deb83efc14f59f700403be74a))
|
|
30
|
+
* **transport:** Auto-Nav 정교화 — polygon SAT 충돌 검사 ([329c44b](https://github.com/things-scene/operato-scene/commit/329c44baae53123befcf6b59606667ecf9b40a5c))
|
|
31
|
+
* **transport:** Auto-Nav 정교화 — replan strategy ([9938378](https://github.com/things-scene/operato-scene/commit/9938378f53dc3ce8580a74adf17c8ce7a58c63c9))
|
|
32
|
+
* **transport:** Facility↔Z — Staircase / CarrierElevator vertical edges ([d96655a](https://github.com/things-scene/operato-scene/commit/d96655aa33ba581d46377e3c5e3a237e354aa329))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### :bug: Bug Fix
|
|
36
|
+
|
|
37
|
+
* **i18n:** nature label kebab-case 통일 — ox-property-editor 의 단순 prefix 매칭 ([6ca60e3](https://github.com/things-scene/operato-scene/commit/6ca60e318a1f601fc3999f1bfaf8594557eaf3cf))
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
### :memo: Documentation
|
|
41
|
+
|
|
42
|
+
* **transport:** AMR-8 — 가이드 + templates 추가 ([55b35ed](https://github.com/things-scene/operato-scene/commit/55b35edb6d1bb96e226db24a2c38d6988018f97f))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
6
46
|
## [10.0.0-beta.46](https://github.com/things-scene/operato-scene/compare/v10.0.0-beta.45...v10.0.0-beta.46) (2026-05-24)
|
|
7
47
|
|
|
8
48
|
**Note:** Version bump only for package @operato/scene-transport
|
package/dist/agv.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
|
|
2
|
-
import type { SlotDef, State, Material3D } from '@hatiolab/things-scene';
|
|
2
|
+
import type { SlotDef, State, Material3D, PathPlan, PathPlanOptions, Reservation, SpeedProfile, Navigable } from '@hatiolab/things-scene';
|
|
3
3
|
import { type Alignment, type CarrierAttachPoint, type Heights, type LegendBinding, type MoveOptions, type PlacementArchetype } from '@operato/scene-base';
|
|
4
|
+
type Vec3 = {
|
|
5
|
+
x: number;
|
|
6
|
+
y: number;
|
|
7
|
+
z: number;
|
|
8
|
+
};
|
|
4
9
|
/**
|
|
5
10
|
* Agv status — common to both payload and towing AGVs (kept narrow on purpose).
|
|
6
11
|
*
|
|
@@ -20,6 +25,12 @@ export interface AgvState extends State {
|
|
|
20
25
|
status?: AgvStatus;
|
|
21
26
|
battery?: number;
|
|
22
27
|
speed?: number;
|
|
28
|
+
/** 자율 path planning 활성화. default false — backward compat. true 시 NavGraph 활용. */
|
|
29
|
+
autonomousPathPlanning?: boolean;
|
|
30
|
+
/** Grid layer cell size (자동 NavGraph 의 정밀도). */
|
|
31
|
+
gridCellSize?: number;
|
|
32
|
+
/** SpeedProfile — 명시 시 path duration 계산에 사용. */
|
|
33
|
+
speedProfile?: SpeedProfile;
|
|
23
34
|
material3d?: Material3D;
|
|
24
35
|
}
|
|
25
36
|
declare const Base: typeof Component & {
|
|
@@ -30,6 +41,8 @@ declare const Base: typeof Component & {
|
|
|
30
41
|
pick(carrier: Component, options?: MoveOptions): Promise<void>;
|
|
31
42
|
place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>;
|
|
32
43
|
pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>;
|
|
44
|
+
moveTo(target: Component, options?: MoveOptions): Promise<void>;
|
|
45
|
+
canReach(target: any): boolean;
|
|
33
46
|
};
|
|
34
47
|
};
|
|
35
48
|
/**
|
|
@@ -44,7 +57,7 @@ declare const Base: typeof Component & {
|
|
|
44
57
|
*
|
|
45
58
|
* For the towing variant (no cargo deck, pulls trailers behind), see Tugger.
|
|
46
59
|
*/
|
|
47
|
-
export default class Agv extends Base {
|
|
60
|
+
export default class Agv extends Base implements Navigable {
|
|
48
61
|
get state(): AgvState;
|
|
49
62
|
/**
|
|
50
63
|
* Phase H — pickup contract 호환성 type. AGV 는 carrier 를 deck 에 올리는
|
|
@@ -55,6 +68,14 @@ export default class Agv extends Base {
|
|
|
55
68
|
* TS4113 회피.)
|
|
56
69
|
*/
|
|
57
70
|
get toolType(): string;
|
|
71
|
+
get speedProfile(): SpeedProfile;
|
|
72
|
+
planPath(target: Vec3, options?: PathPlanOptions): PathPlan | null;
|
|
73
|
+
reservePath(plan: PathPlan, priority?: number): Reservation[] | null;
|
|
74
|
+
followReservation(reservations: Reservation[]): Promise<void>;
|
|
75
|
+
releaseReservation(reservations: Reservation[]): void;
|
|
76
|
+
private _lastPathPlan;
|
|
77
|
+
moveTo(target: Component, options?: MoveOptions): Promise<void>;
|
|
78
|
+
canReach(target: any): boolean;
|
|
58
79
|
static legends: Record<string, LegendBinding>;
|
|
59
80
|
/**
|
|
60
81
|
* AGV sits on its wheels — `floor` archetype. Default depth = operation,
|
package/dist/agv.js
CHANGED
|
@@ -5,6 +5,7 @@ import { __decorate } from "tslib";
|
|
|
5
5
|
import { ContainerAbstract, ContainerCapacity, sceneComponent } from '@hatiolab/things-scene';
|
|
6
6
|
import { CarrierHolder, FloorBound, Legendable, Mover, Placeable } from '@operato/scene-base';
|
|
7
7
|
import { Agv3D } from './agv-3d.js';
|
|
8
|
+
import { navigablePlanPath, navigableReservePath, navigableReleaseReservation, navigableFollowReservation, navigableCanReach, resolveTargetWorldPosition, defaultSpeedProfile } from './navigable-helpers.js';
|
|
8
9
|
/**
|
|
9
10
|
* Body color — neutral industrial gray base, slightly modulated by status.
|
|
10
11
|
* AGVs typically have status-color LED strips rather than full body color
|
|
@@ -107,6 +108,90 @@ let Agv = class Agv extends Base {
|
|
|
107
108
|
get toolType() {
|
|
108
109
|
return 'agv-deck';
|
|
109
110
|
}
|
|
111
|
+
// ── Phase J Navigable interface — AN-PR-2.1+ 통합 ─────────────────────
|
|
112
|
+
//
|
|
113
|
+
// AGV 가 *_state.autonomousPathPlanning=true_* 시 NavGraph 기반 자율 path 활용.
|
|
114
|
+
// default false — 기존 직선 Mover.moveTo 그대로 동작 (backward compat).
|
|
115
|
+
get speedProfile() {
|
|
116
|
+
const s = this.state;
|
|
117
|
+
if (s.speedProfile)
|
|
118
|
+
return s.speedProfile;
|
|
119
|
+
return defaultSpeedProfile(s.speed ?? 200);
|
|
120
|
+
}
|
|
121
|
+
planPath(target, options) {
|
|
122
|
+
return navigablePlanPath({
|
|
123
|
+
mover: this, target, options,
|
|
124
|
+
speedProfile: this.speedProfile,
|
|
125
|
+
gridCellSize: this.state?.gridCellSize,
|
|
126
|
+
toolType: this.toolType
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
reservePath(plan, priority) {
|
|
130
|
+
return navigableReservePath(this, plan, priority);
|
|
131
|
+
}
|
|
132
|
+
async followReservation(reservations) {
|
|
133
|
+
const plan = this._lastPathPlan;
|
|
134
|
+
this.setState({ status: 'moving' });
|
|
135
|
+
if (!plan || plan.segments.length === 0) {
|
|
136
|
+
for (const r of reservations) {
|
|
137
|
+
const duration = r.timeWindow.endSimMs - r.timeWindow.startSimMs;
|
|
138
|
+
if (duration > 0)
|
|
139
|
+
await new Promise(resolve => setTimeout(resolve, duration));
|
|
140
|
+
}
|
|
141
|
+
this.releaseReservation(reservations);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
await navigableFollowReservation({
|
|
145
|
+
mover: this, plan, reservations,
|
|
146
|
+
superMoveTo: (target, options) => super.moveTo(target, options)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
releaseReservation(reservations) {
|
|
150
|
+
navigableReleaseReservation(reservations);
|
|
151
|
+
}
|
|
152
|
+
_lastPathPlan = null;
|
|
153
|
+
// ── Mover overrides ───────────────────────────────────────────────────
|
|
154
|
+
async moveTo(target, options) {
|
|
155
|
+
const autonomous = this.state.autonomousPathPlanning === true;
|
|
156
|
+
if (!autonomous) {
|
|
157
|
+
return super.moveTo(target, options); // backward compat — 직선
|
|
158
|
+
}
|
|
159
|
+
const targetPos = resolveTargetWorldPosition(target);
|
|
160
|
+
if (!targetPos)
|
|
161
|
+
return super.moveTo(target, options);
|
|
162
|
+
const plan = this.planPath(targetPos, { priority: options?.priority ?? 0 });
|
|
163
|
+
if (!plan) {
|
|
164
|
+
this._lastPathPlan = null;
|
|
165
|
+
return super.moveTo(target, options);
|
|
166
|
+
}
|
|
167
|
+
this._lastPathPlan = plan;
|
|
168
|
+
const reservations = this.reservePath(plan, options?.priority ?? 0);
|
|
169
|
+
if (!reservations) {
|
|
170
|
+
this._lastPathPlan = null;
|
|
171
|
+
await new Promise(r => setTimeout(r, 500 + Math.random() * 500));
|
|
172
|
+
return super.moveTo(target, options);
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
await this.followReservation(reservations);
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
this._lastPathPlan = null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
canReach(target) {
|
|
182
|
+
const autonomous = this.state.autonomousPathPlanning === true;
|
|
183
|
+
if (!autonomous)
|
|
184
|
+
return super.canReach(target);
|
|
185
|
+
const result = navigableCanReach({
|
|
186
|
+
mover: this, target,
|
|
187
|
+
speedProfile: this.speedProfile,
|
|
188
|
+
gridCellSize: this.state?.gridCellSize,
|
|
189
|
+
toolType: this.toolType
|
|
190
|
+
});
|
|
191
|
+
if (result === null)
|
|
192
|
+
return super.canReach(target);
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
110
195
|
static legends = {
|
|
111
196
|
bodyColor: { from: 'status', legend: BODY_LEGEND },
|
|
112
197
|
lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }
|
package/dist/agv.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agv.js","sourceRoot":"","sources":["../src/agv.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, ContainerAbstract, ContainerCapacity, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { SlotDef, State, Material3D } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n FloorBound,\n Legendable,\n Mover,\n Placeable,\n type Alignment,\n type CarrierAttachPoint,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Agv3D } from './agv-3d.js'\n\n/**\n * Agv status — common to both payload and towing AGVs (kept narrow on purpose).\n *\n * - `idle` — parked, awaiting task\n * - `moving` — driving along a path / executing transport\n * - `charging` — at a charging dock / battery station\n * - `error` — fault / blocked / e-stop\n *\n * Loaded-vs-empty distinction is *not* in the status enum because for a\n * Kiva-style payload AGV it's already obvious from the children (cargo\n * components) parented to it — duplicating that as a status flag would\n * invite drift.\n */\nexport type AgvStatus = 'idle' | 'moving' | 'charging' | 'error'\n\n/** Agv 컴포넌트 state */\nexport interface AgvState extends State {\n // ── 운영 상태 ──\n status?: AgvStatus\n\n // ── 시뮬레이션/모니터링 ──\n battery?: number\n speed?: number\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\n/**\n * Body color — neutral industrial gray base, slightly modulated by status.\n * AGVs typically have status-color LED strips rather than full body color\n * change; the body legend stays subtle so the LED strip + lamp do the\n * communicating.\n */\nconst BODY_LEGEND = {\n idle: '#999',\n moving: '#aaa',\n charging: '#aaa',\n error: '#c66',\n default: '#999'\n}\n\n/**\n * LED strip emissive — the dominant status indicator for AGVs (typically a\n * color-band running around the chassis perimeter).\n */\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44', // green (operating)\n charging: '#ffaa00', // amber (charging)\n error: '#ff3333', // red\n default: '#222222'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Moving', value: 'moving' },\n { display: 'Charging', value: 'charging' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'battery',\n name: 'battery',\n placeholder: '0..1'\n },\n {\n // AGV travel speed in scene units / second. Used by Mover.moveTo\n // to derive motion duration. Tune per board scale; forklift convention.\n type: 'number',\n label: 'speed',\n name: 'speed',\n placeholder: 'units/sec'\n }\n ],\n help: 'scene/component/agv'\n}\n\n// Composition:\n// FloorBound → Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract\n//\n// ContainerCapacity: receive() / dispatch() / canReceive() / slots — Mover.pick uses\n// receive() for slot-tracking and event emission on every cargo transfer.\n// CarrierHolder: attachPointFor() — deck-top 3D mount frame; Carriable.added()\n// calls applyHolderAttachPoint() which uses our override for 3D positioning.\n// Mover: moveTo / pick / place / pickAndPlace / executeMission.\n// FloorBound: outermost rotation guard.\n//\n// `ContainerAbstract` (not `Container`) — avoids isHTMLElement()=true that would\n// trip the 3D pipeline's addObject DOM-skip gate.\nconst Base = FloorBound(Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract)))))) as unknown as typeof Component & {\n new (...args: any[]): Component & {\n isCarrierHolder: boolean\n isMover: boolean\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined\n pick(carrier: Component, options?: MoveOptions): Promise<void>\n place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n }\n}\n\n/**\n * Agv — payload (unit-load) automated guided vehicle. The Kiva-style flat-deck\n * AGV that drives under or carries cargo to/from operation surfaces.\n *\n * **Container-based for cargo containment.** A payload Agv has a flat top\n * deck whose surface is at the scene's operation height. Boxes, parcels,\n * loaded pallets (anything with `placement: 'operation'`) can be added as\n * children — when they are, their natural archetype-derived zPos puts them\n * exactly on the AGV's deck (since AGV depth = operation - floor).\n *\n * For the towing variant (no cargo deck, pulls trailers behind), see Tugger.\n */\n@sceneComponent('agv')\nexport default class Agv extends Base {\n // `Base` is cast through `typeof Component` so TS sees `state` as an\n // accessor; `declare state: …` would conflict with TS2610. Override the\n // getter instead — runtime behavior is identical (just delegates to super).\n override get state(): AgvState {\n return super.state as AgvState\n }\n\n /**\n * Phase H — pickup contract 호환성 type. AGV 는 carrier 를 deck 에 올리는\n * 식이라 carrier 가 'agv-deck' 진입을 노출하면 매칭. (예: pallet 위 AGV 가\n * 자체 deck 으로 pallet 들어올리는 시나리오)\n *\n * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아\n * TS4113 회피.)\n */\n get toolType(): string {\n return 'agv-deck'\n }\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n /**\n * AGV sits on its wheels — `floor` archetype. Default depth = operation,\n * so the top deck lands at the scene's operation height. This is the\n * design point of payload AGVs: the deck height matches conveyor belt\n * height, equipment ports, and forklift fork height — cargo transfers\n * across all of them at the same level.\n */\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (h: Heights) => h.operation - h.floor\n\n /**\n * Heading yaw offset (rad). things-scene's vehicle convention is\n * `vehicle forward = component-local -Z` (= \"rotation=0 → toward canvas\n * top edge\"); the framework default `Math.PI / 2` aligns with that.\n * Stated explicitly so the model's forward axis is documented at the\n * class level rather than relying silently on the framework default.\n */\n static yawOffset = Math.PI / 2\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n // ── ContainerCapacity ─────────────────────────────────────────────────────\n\n /**\n * Single-cargo policy: AGV 는 한 번에 1개의 carrier 만 운반.\n * 이미 1개를 보유한 상태에서는 `canReceive()` → false → `pick()` 거부.\n */\n get slots(): SlotDef[] {\n return [{ id: 'deck', maxCount: 1 }]\n }\n\n /** Accept logistics packages (placement='operation') as deck cargo. */\n containable(component: Component) {\n const archetype = (component.constructor as any).placement\n if (archetype === 'operation') return true\n return component.isDescendible(this)\n }\n\n /**\n * AGV attach point — flat top deck. Cargo lands on the deck surface\n * (top of the AGV envelope, which is `+depth/2` in component-local Y).\n * Multiple cargo items stack vertically by their own depth.\n *\n * `attach` is the AGV's `_realObject.object3d` directly — there is no\n * inner sub-frame the way Forklift has a fork-tip frame, because the\n * deck moves rigidly with the chassis. Cargo Y reflects the cargoDepth\n * × slot index stack offset.\n */\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined {\n const ro = this._realObject\n if (!ro?.object3d) return undefined\n\n const depth = (this.state.depth as number) ?? 0\n const slotIdx = this._slotIndexOf(carrier)\n const cargoDepth = (carrier.state as any).depth ?? 100\n // 3D origin convention (beta.66+): carrier 의 origin = center.\n // 데크 표면 (= AGV 상단) y = depth/2. carrier 의 bottom 을 데크 표면에 두려면\n // carrier center y = deckY + cargoDepth/2. 이후 slot 별로 cargoDepth 씩 stack.\n const deckY = depth / 2\n\n return {\n attach: ro.object3d,\n localPosition: {\n x: 0,\n y: deckY + cargoDepth / 2 + slotIdx * cargoDepth,\n z: 0\n },\n // Phase F: AGV 가 회전하면 cargo 도 같이 회전 (deck 위 고정 lock).\n carryPolicy: 'follow-holder'\n }\n }\n\n /**\n * Stack slot = the carrier's index among siblings. Stable because\n * children order is preserved; no persisted state needed.\n */\n private _slotIndexOf(carrier: Component): number {\n const children = this.components ?? []\n const idx = children.indexOf(carrier)\n return idx < 0 ? children.length : idx\n }\n\n /**\n * pick 보완: super.pick 후 carrier 의 자세 정렬 + attachPointFor 강제 재적용.\n *\n * 흐름의 마지막에 ContainerCapacity.receive 의 setState({left, top}) 가 표준\n * pipeline 을 트리거해 obj3d.position 이 attachPointFor 의 localPosition 을 덮어씀.\n * (= 첫번째 carrier 가 데크 위가 아니라 잘못된 위치/공중에 잡히는 원인)\n * super.pick 종료 후 attachPointFor 를 다시 적용하고 suppressTransform=true 로\n * pipeline 의 추가 override 차단. AGV 가 움직이면 carrier 는 scene-graph 통해 자동 추종.\n */\n override async pick(carrier: Component, options: any = {}): Promise<void> {\n // 1-capacity 정책: 이미 1개 보유 시 pick 거부 (moveTo / engage 낭비 방지).\n if (!(this as any).canReceive(carrier)) {\n this.trigger('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'agv-at-capacity'\n })\n return\n }\n await super.pick(carrier, options)\n carrier.setState?.({ rotation: 0, rotationX: 0, rotationY: 0 })\n this._snapToAttachPoint(carrier)\n }\n\n private _snapToAttachPoint(carrier: Component): void {\n const point = this.attachPointFor(carrier)\n const ro = (carrier as any)._realObject\n const obj3d = ro?.object3d\n if (!obj3d || !point?.localPosition) return\n const lp = point.localPosition\n obj3d.position.set(lp.x, lp.y, lp.z)\n if (point.localRotation) {\n obj3d.rotation.set(point.localRotation.x, point.localRotation.y, point.localRotation.z)\n } else {\n obj3d.quaternion.identity()\n }\n if (ro) ro.suppressTransform = true\n }\n\n /**\n * 2D — render() sets up the rounded chassis path; the framework fills it\n * with `fillStyle` (= bodyColor from Legendable) and strokes with\n * `strokeStyle`. Additional top-down details (bumpers, LiDAR, lift pad,\n * direction triangle) are drawn in `postrender()`.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const radius = Math.min(width, height) * 0.12\n ctx.beginPath()\n ctx.roundRect(left, top, width, height, radius)\n }\n\n /** Top-view accent details — bumpers, lift pad, LiDAR, direction triangle. */\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const cx = left + width / 2\n const cy = top + height / 2\n const accentColor = (this.state.lampEmissive as string) || '#44ff44'\n\n ctx.save()\n\n // Hi-vis bumper strips (front + rear)\n const bumperT = Math.min(width, height) * 0.06\n ctx.fillStyle = '#eeaa00'\n ctx.fillRect(left + width * 0.15, top, width * 0.7, bumperT)\n ctx.fillRect(left + width * 0.15, top + height - bumperT, width * 0.7, bumperT)\n ctx.fillStyle = '#111'\n ctx.fillRect(left + width * 0.02, top, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.85, top, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.02, top + height - bumperT, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.85, top + height - bumperT, width * 0.13, bumperT)\n\n // Lift pad (center circle — Kiva-style under-shelf lift)\n const padR = Math.min(width, height) * 0.22\n ctx.fillStyle = '#4a4a55'\n ctx.strokeStyle = '#222'\n ctx.lineWidth = 1\n ctx.beginPath()\n ctx.ellipse(cx, cy, padR, padR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // LiDAR sensor (small filled circle near front)\n const lidarR = Math.min(width, height) * 0.07\n ctx.fillStyle = '#222233'\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.20, lidarR, lidarR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n // Status hint on LiDAR\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.20, lidarR * 0.4, lidarR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n // Direction-of-travel triangle\n ctx.fillStyle = accentColor\n ctx.strokeStyle = '#111'\n ctx.beginPath()\n ctx.moveTo(cx, top + height * 0.06)\n ctx.lineTo(cx - width * 0.06, top + height * 0.13)\n ctx.lineTo(cx + width * 0.06, top + height * 0.13)\n ctx.closePath()\n ctx.fill()\n ctx.stroke()\n\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Agv3D(this)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agv.js","sourceRoot":"","sources":["../src/agv.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, ContainerAbstract, ContainerCapacity, RealObject, sceneComponent } from '@hatiolab/things-scene'\nimport type { SlotDef, State, Material3D, PathPlan, PathPlanOptions, Reservation, SpeedProfile, Navigable } from '@hatiolab/things-scene'\nimport {\n CarrierHolder,\n FloorBound,\n Legendable,\n Mover,\n Placeable,\n type Alignment,\n type CarrierAttachPoint,\n type Heights,\n type LegendBinding,\n type MoveOptions,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Agv3D } from './agv-3d.js'\nimport {\n navigablePlanPath, navigableReservePath, navigableReleaseReservation,\n navigableFollowReservation, navigableCanReach,\n resolveTargetWorldPosition, defaultSpeedProfile\n} from './navigable-helpers.js'\n\ntype Vec3 = { x: number; y: number; z: number }\n\n/**\n * Agv status — common to both payload and towing AGVs (kept narrow on purpose).\n *\n * - `idle` — parked, awaiting task\n * - `moving` — driving along a path / executing transport\n * - `charging` — at a charging dock / battery station\n * - `error` — fault / blocked / e-stop\n *\n * Loaded-vs-empty distinction is *not* in the status enum because for a\n * Kiva-style payload AGV it's already obvious from the children (cargo\n * components) parented to it — duplicating that as a status flag would\n * invite drift.\n */\nexport type AgvStatus = 'idle' | 'moving' | 'charging' | 'error'\n\n/** Agv 컴포넌트 state */\nexport interface AgvState extends State {\n // ── 운영 상태 ──\n status?: AgvStatus\n\n // ── 시뮬레이션/모니터링 ──\n battery?: number\n speed?: number\n\n // ── Phase J Navigable (AN-PR-2.1+ 통합) ──\n /** 자율 path planning 활성화. default false — backward compat. true 시 NavGraph 활용. */\n autonomousPathPlanning?: boolean\n /** Grid layer cell size (자동 NavGraph 의 정밀도). */\n gridCellSize?: number\n /** SpeedProfile — 명시 시 path duration 계산에 사용. */\n speedProfile?: SpeedProfile\n\n // ── 3D 재질 ──\n material3d?: Material3D\n}\n\n/**\n * Body color — neutral industrial gray base, slightly modulated by status.\n * AGVs typically have status-color LED strips rather than full body color\n * change; the body legend stays subtle so the LED strip + lamp do the\n * communicating.\n */\nconst BODY_LEGEND = {\n idle: '#999',\n moving: '#aaa',\n charging: '#aaa',\n error: '#c66',\n default: '#999'\n}\n\n/**\n * LED strip emissive — the dominant status indicator for AGVs (typically a\n * color-band running around the chassis perimeter).\n */\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44', // green (operating)\n charging: '#ffaa00', // amber (charging)\n error: '#ff3333', // red\n default: '#222222'\n}\n\nconst NATURE: ComponentNature = {\n mutable: false,\n resizable: true,\n rotatable: true,\n properties: [\n {\n type: 'select',\n label: 'status',\n name: 'status',\n property: {\n options: [\n { display: 'Idle', value: 'idle' },\n { display: 'Moving', value: 'moving' },\n { display: 'Charging', value: 'charging' },\n { display: 'Error', value: 'error' }\n ]\n }\n },\n {\n type: 'number',\n label: 'battery',\n name: 'battery',\n placeholder: '0..1'\n },\n {\n // AGV travel speed in scene units / second. Used by Mover.moveTo\n // to derive motion duration. Tune per board scale; forklift convention.\n type: 'number',\n label: 'speed',\n name: 'speed',\n placeholder: 'units/sec'\n }\n ],\n help: 'scene/component/agv'\n}\n\n// Composition:\n// FloorBound → Mover → CarrierHolder → ContainerCapacity → Legendable → Placeable → ContainerAbstract\n//\n// ContainerCapacity: receive() / dispatch() / canReceive() / slots — Mover.pick uses\n// receive() for slot-tracking and event emission on every cargo transfer.\n// CarrierHolder: attachPointFor() — deck-top 3D mount frame; Carriable.added()\n// calls applyHolderAttachPoint() which uses our override for 3D positioning.\n// Mover: moveTo / pick / place / pickAndPlace / executeMission.\n// FloorBound: outermost rotation guard.\n//\n// `ContainerAbstract` (not `Container`) — avoids isHTMLElement()=true that would\n// trip the 3D pipeline's addObject DOM-skip gate.\nconst Base = FloorBound(Mover(CarrierHolder(ContainerCapacity(Legendable(Placeable(ContainerAbstract)))))) as unknown as typeof Component & {\n new (...args: any[]): Component & {\n isCarrierHolder: boolean\n isMover: boolean\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined\n pick(carrier: Component, options?: MoveOptions): Promise<void>\n place(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n pickAndPlace(carrier: Component, holder: Component, options?: MoveOptions): Promise<void>\n moveTo(target: Component, options?: MoveOptions): Promise<void>\n canReach(target: any): boolean\n }\n}\n\n/**\n * Agv — payload (unit-load) automated guided vehicle. The Kiva-style flat-deck\n * AGV that drives under or carries cargo to/from operation surfaces.\n *\n * **Container-based for cargo containment.** A payload Agv has a flat top\n * deck whose surface is at the scene's operation height. Boxes, parcels,\n * loaded pallets (anything with `placement: 'operation'`) can be added as\n * children — when they are, their natural archetype-derived zPos puts them\n * exactly on the AGV's deck (since AGV depth = operation - floor).\n *\n * For the towing variant (no cargo deck, pulls trailers behind), see Tugger.\n */\n@sceneComponent('agv')\nexport default class Agv extends Base implements Navigable {\n // `Base` is cast through `typeof Component` so TS sees `state` as an\n // accessor; `declare state: …` would conflict with TS2610. Override the\n // getter instead — runtime behavior is identical (just delegates to super).\n override get state(): AgvState {\n return super.state as AgvState\n }\n\n /**\n * Phase H — pickup contract 호환성 type. AGV 는 carrier 를 deck 에 올리는\n * 식이라 carrier 가 'agv-deck' 진입을 노출하면 매칭. (예: pallet 위 AGV 가\n * 자체 deck 으로 pallet 들어올리는 시나리오)\n *\n * (override 키워드 미사용 — Base 캐스트가 Mover 의 toolType 을 노출하지 않아\n * TS4113 회피.)\n */\n get toolType(): string {\n return 'agv-deck'\n }\n\n // ── Phase J Navigable interface — AN-PR-2.1+ 통합 ─────────────────────\n //\n // AGV 가 *_state.autonomousPathPlanning=true_* 시 NavGraph 기반 자율 path 활용.\n // default false — 기존 직선 Mover.moveTo 그대로 동작 (backward compat).\n\n get speedProfile(): SpeedProfile {\n const s = this.state as AgvState\n if (s.speedProfile) return s.speedProfile\n return defaultSpeedProfile(s.speed ?? 200)\n }\n\n planPath(target: Vec3, options?: PathPlanOptions): PathPlan | null {\n return navigablePlanPath({\n mover: this, target, options,\n speedProfile: this.speedProfile,\n gridCellSize: (this.state as AgvState)?.gridCellSize,\n toolType: this.toolType\n })\n }\n\n reservePath(plan: PathPlan, priority?: number): Reservation[] | null {\n return navigableReservePath(this, plan, priority)\n }\n\n async followReservation(reservations: Reservation[]): Promise<void> {\n const plan = this._lastPathPlan\n this.setState({ status: 'moving' as AgvStatus } as any)\n if (!plan || plan.segments.length === 0) {\n for (const r of reservations) {\n const duration = r.timeWindow.endSimMs - r.timeWindow.startSimMs\n if (duration > 0) await new Promise(resolve => setTimeout(resolve, duration))\n }\n this.releaseReservation(reservations)\n return\n }\n await navigableFollowReservation({\n mover: this, plan, reservations,\n superMoveTo: (target, options) => super.moveTo(target, options)\n })\n }\n\n releaseReservation(reservations: Reservation[]): void {\n navigableReleaseReservation(reservations)\n }\n\n private _lastPathPlan: PathPlan | null = null\n\n // ── Mover overrides ───────────────────────────────────────────────────\n\n override async moveTo(target: Component, options?: MoveOptions): Promise<void> {\n const autonomous = (this.state as AgvState).autonomousPathPlanning === true\n if (!autonomous) {\n return super.moveTo(target, options) // backward compat — 직선\n }\n const targetPos = resolveTargetWorldPosition(target)\n if (!targetPos) return super.moveTo(target, options)\n\n const plan = this.planPath(targetPos, { priority: (options as any)?.priority ?? 0 })\n if (!plan) {\n this._lastPathPlan = null\n return super.moveTo(target, options)\n }\n this._lastPathPlan = plan\n\n const reservations = this.reservePath(plan, (options as any)?.priority ?? 0)\n if (!reservations) {\n this._lastPathPlan = null\n await new Promise(r => setTimeout(r, 500 + Math.random() * 500))\n return super.moveTo(target, options)\n }\n try {\n await this.followReservation(reservations)\n } finally {\n this._lastPathPlan = null\n }\n }\n\n canReach(target: any): boolean {\n const autonomous = (this.state as AgvState).autonomousPathPlanning === true\n if (!autonomous) return super.canReach(target)\n const result = navigableCanReach({\n mover: this, target,\n speedProfile: this.speedProfile,\n gridCellSize: (this.state as AgvState)?.gridCellSize,\n toolType: this.toolType\n })\n if (result === null) return super.canReach(target)\n return result\n }\n\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n /**\n * AGV sits on its wheels — `floor` archetype. Default depth = operation,\n * so the top deck lands at the scene's operation height. This is the\n * design point of payload AGVs: the deck height matches conveyor belt\n * height, equipment ports, and forklift fork height — cargo transfers\n * across all of them at the same level.\n */\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = (h: Heights) => h.operation - h.floor\n\n /**\n * Heading yaw offset (rad). things-scene's vehicle convention is\n * `vehicle forward = component-local -Z` (= \"rotation=0 → toward canvas\n * top edge\"); the framework default `Math.PI / 2` aligns with that.\n * Stated explicitly so the model's forward axis is documented at the\n * class level rather than relying silently on the framework default.\n */\n static yawOffset = Math.PI / 2\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n // ── ContainerCapacity ─────────────────────────────────────────────────────\n\n /**\n * Single-cargo policy: AGV 는 한 번에 1개의 carrier 만 운반.\n * 이미 1개를 보유한 상태에서는 `canReceive()` → false → `pick()` 거부.\n */\n get slots(): SlotDef[] {\n return [{ id: 'deck', maxCount: 1 }]\n }\n\n /** Accept logistics packages (placement='operation') as deck cargo. */\n containable(component: Component) {\n const archetype = (component.constructor as any).placement\n if (archetype === 'operation') return true\n return component.isDescendible(this)\n }\n\n /**\n * AGV attach point — flat top deck. Cargo lands on the deck surface\n * (top of the AGV envelope, which is `+depth/2` in component-local Y).\n * Multiple cargo items stack vertically by their own depth.\n *\n * `attach` is the AGV's `_realObject.object3d` directly — there is no\n * inner sub-frame the way Forklift has a fork-tip frame, because the\n * deck moves rigidly with the chassis. Cargo Y reflects the cargoDepth\n * × slot index stack offset.\n */\n attachPointFor(carrier: Component): CarrierAttachPoint | undefined {\n const ro = this._realObject\n if (!ro?.object3d) return undefined\n\n const depth = (this.state.depth as number) ?? 0\n const slotIdx = this._slotIndexOf(carrier)\n const cargoDepth = (carrier.state as any).depth ?? 100\n // 3D origin convention (beta.66+): carrier 의 origin = center.\n // 데크 표면 (= AGV 상단) y = depth/2. carrier 의 bottom 을 데크 표면에 두려면\n // carrier center y = deckY + cargoDepth/2. 이후 slot 별로 cargoDepth 씩 stack.\n const deckY = depth / 2\n\n return {\n attach: ro.object3d,\n localPosition: {\n x: 0,\n y: deckY + cargoDepth / 2 + slotIdx * cargoDepth,\n z: 0\n },\n // Phase F: AGV 가 회전하면 cargo 도 같이 회전 (deck 위 고정 lock).\n carryPolicy: 'follow-holder'\n }\n }\n\n /**\n * Stack slot = the carrier's index among siblings. Stable because\n * children order is preserved; no persisted state needed.\n */\n private _slotIndexOf(carrier: Component): number {\n const children = this.components ?? []\n const idx = children.indexOf(carrier)\n return idx < 0 ? children.length : idx\n }\n\n /**\n * pick 보완: super.pick 후 carrier 의 자세 정렬 + attachPointFor 강제 재적용.\n *\n * 흐름의 마지막에 ContainerCapacity.receive 의 setState({left, top}) 가 표준\n * pipeline 을 트리거해 obj3d.position 이 attachPointFor 의 localPosition 을 덮어씀.\n * (= 첫번째 carrier 가 데크 위가 아니라 잘못된 위치/공중에 잡히는 원인)\n * super.pick 종료 후 attachPointFor 를 다시 적용하고 suppressTransform=true 로\n * pipeline 의 추가 override 차단. AGV 가 움직이면 carrier 는 scene-graph 통해 자동 추종.\n */\n override async pick(carrier: Component, options: any = {}): Promise<void> {\n // 1-capacity 정책: 이미 1개 보유 시 pick 거부 (moveTo / engage 낭비 방지).\n if (!(this as any).canReceive(carrier)) {\n this.trigger('transfer-rejected', {\n type: 'transfer-rejected',\n component: carrier,\n container: this,\n reason: 'agv-at-capacity'\n })\n return\n }\n await super.pick(carrier, options)\n carrier.setState?.({ rotation: 0, rotationX: 0, rotationY: 0 })\n this._snapToAttachPoint(carrier)\n }\n\n private _snapToAttachPoint(carrier: Component): void {\n const point = this.attachPointFor(carrier)\n const ro = (carrier as any)._realObject\n const obj3d = ro?.object3d\n if (!obj3d || !point?.localPosition) return\n const lp = point.localPosition\n obj3d.position.set(lp.x, lp.y, lp.z)\n if (point.localRotation) {\n obj3d.rotation.set(point.localRotation.x, point.localRotation.y, point.localRotation.z)\n } else {\n obj3d.quaternion.identity()\n }\n if (ro) ro.suppressTransform = true\n }\n\n /**\n * 2D — render() sets up the rounded chassis path; the framework fills it\n * with `fillStyle` (= bodyColor from Legendable) and strokes with\n * `strokeStyle`. Additional top-down details (bumpers, LiDAR, lift pad,\n * direction triangle) are drawn in `postrender()`.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const radius = Math.min(width, height) * 0.12\n ctx.beginPath()\n ctx.roundRect(left, top, width, height, radius)\n }\n\n /** Top-view accent details — bumpers, lift pad, LiDAR, direction triangle. */\n postrender(ctx: CanvasRenderingContext2D) {\n super.postrender?.(ctx)\n\n const { width, height, left, top } = this.state\n const cx = left + width / 2\n const cy = top + height / 2\n const accentColor = (this.state.lampEmissive as string) || '#44ff44'\n\n ctx.save()\n\n // Hi-vis bumper strips (front + rear)\n const bumperT = Math.min(width, height) * 0.06\n ctx.fillStyle = '#eeaa00'\n ctx.fillRect(left + width * 0.15, top, width * 0.7, bumperT)\n ctx.fillRect(left + width * 0.15, top + height - bumperT, width * 0.7, bumperT)\n ctx.fillStyle = '#111'\n ctx.fillRect(left + width * 0.02, top, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.85, top, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.02, top + height - bumperT, width * 0.13, bumperT)\n ctx.fillRect(left + width * 0.85, top + height - bumperT, width * 0.13, bumperT)\n\n // Lift pad (center circle — Kiva-style under-shelf lift)\n const padR = Math.min(width, height) * 0.22\n ctx.fillStyle = '#4a4a55'\n ctx.strokeStyle = '#222'\n ctx.lineWidth = 1\n ctx.beginPath()\n ctx.ellipse(cx, cy, padR, padR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // LiDAR sensor (small filled circle near front)\n const lidarR = Math.min(width, height) * 0.07\n ctx.fillStyle = '#222233'\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.20, lidarR, lidarR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n // Status hint on LiDAR\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.20, lidarR * 0.4, lidarR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n // Direction-of-travel triangle\n ctx.fillStyle = accentColor\n ctx.strokeStyle = '#111'\n ctx.beginPath()\n ctx.moveTo(cx, top + height * 0.06)\n ctx.lineTo(cx - width * 0.06, top + height * 0.13)\n ctx.lineTo(cx + width * 0.06, top + height * 0.13)\n ctx.closePath()\n ctx.fill()\n ctx.stroke()\n\n ctx.restore()\n }\n\n buildRealObject(): RealObject | undefined {\n return new Agv3D(this)\n }\n}\n"]}
|
package/dist/amr-3d.d.ts
ADDED
package/dist/amr-3d.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* AMR 3D — Autonomous Mobile Robot.
|
|
5
|
+
*
|
|
6
|
+
* AGV 와 구별되는 visual cue:
|
|
7
|
+
* - 360° LiDAR (회전식 dome — 자율 인식의 상징)
|
|
8
|
+
* - 사면 카메라 / 센서 indicator
|
|
9
|
+
* - "AUTO" 또는 자율 status LED
|
|
10
|
+
*
|
|
11
|
+
* Lo-poly 베이스 — AGV 와 동일 패턴 (rounded chassis + deck + wheels) + 자율
|
|
12
|
+
* 차별점만 강조.
|
|
13
|
+
*/
|
|
14
|
+
import * as THREE from 'three';
|
|
15
|
+
import { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js';
|
|
16
|
+
import { RealObjectGroup } from '@hatiolab/things-scene';
|
|
17
|
+
const CHASSIS_COLOR = 0x4a4a55;
|
|
18
|
+
const DECK_COLOR = 0x666677;
|
|
19
|
+
const LIDAR_BODY = 0x222233;
|
|
20
|
+
const LIDAR_DOME = 0x66aaff; // AMR 의 LiDAR — 푸른빛 (AGV 의 회색 dome 과 구별)
|
|
21
|
+
const BUMPER_COLOR = 0xeeaa00;
|
|
22
|
+
const WHEEL_COLOR = 0x202020;
|
|
23
|
+
const SENSOR_COLOR = 0x445566;
|
|
24
|
+
export class AMR3D extends RealObjectGroup {
|
|
25
|
+
build() {
|
|
26
|
+
super.build();
|
|
27
|
+
const state = this.component.state;
|
|
28
|
+
const width = state.width ?? 800;
|
|
29
|
+
const height = state.height ?? 600;
|
|
30
|
+
const depth = this.effectiveDepth ?? 300;
|
|
31
|
+
const bodyColor = state.bodyColor ?? '#888';
|
|
32
|
+
const emissiveColor = state.lampEmissive ?? '#222';
|
|
33
|
+
const status = state.status;
|
|
34
|
+
const chassisHeight = depth * 0.5;
|
|
35
|
+
const deckHeight = depth * 0.15;
|
|
36
|
+
const wheelHeight = depth * 0.35;
|
|
37
|
+
// ─ Chassis (rounded box) ─
|
|
38
|
+
const chassisGeo = new RoundedBoxGeometry(width, chassisHeight, height, 2, depth * 0.08);
|
|
39
|
+
const chassisMat = new THREE.MeshStandardMaterial({
|
|
40
|
+
color: new THREE.Color(bodyColor),
|
|
41
|
+
roughness: 0.5,
|
|
42
|
+
metalness: 0.3
|
|
43
|
+
});
|
|
44
|
+
const chassis = new THREE.Mesh(chassisGeo, chassisMat);
|
|
45
|
+
chassis.position.y = -depth / 2 + wheelHeight + chassisHeight / 2;
|
|
46
|
+
this.object3d.add(chassis);
|
|
47
|
+
// ─ Top deck (cargo surface) ─
|
|
48
|
+
const deckGeo = new RoundedBoxGeometry(width * 0.95, deckHeight, height * 0.95, 2, depth * 0.04);
|
|
49
|
+
const deckMat = new THREE.MeshStandardMaterial({
|
|
50
|
+
color: DECK_COLOR,
|
|
51
|
+
roughness: 0.6
|
|
52
|
+
});
|
|
53
|
+
const deck = new THREE.Mesh(deckGeo, deckMat);
|
|
54
|
+
deck.position.y = chassis.position.y + chassisHeight / 2 + deckHeight / 2;
|
|
55
|
+
this.object3d.add(deck);
|
|
56
|
+
// ─ LiDAR — 360° rotating dome (AMR 의 상징) ─
|
|
57
|
+
const lidarRadius = Math.min(width, height) * 0.08;
|
|
58
|
+
const lidarBodyGeo = new THREE.CylinderGeometry(lidarRadius, lidarRadius, lidarRadius * 1.2, 16);
|
|
59
|
+
const lidarBodyMat = new THREE.MeshStandardMaterial({
|
|
60
|
+
color: LIDAR_BODY,
|
|
61
|
+
roughness: 0.4,
|
|
62
|
+
metalness: 0.6
|
|
63
|
+
});
|
|
64
|
+
const lidarBody = new THREE.Mesh(lidarBodyGeo, lidarBodyMat);
|
|
65
|
+
lidarBody.position.set(0, deck.position.y + deckHeight / 2 + lidarRadius * 0.6, 0);
|
|
66
|
+
this.object3d.add(lidarBody);
|
|
67
|
+
const lidarDomeGeo = new THREE.SphereGeometry(lidarRadius * 1.05, 16, 16, 0, Math.PI * 2, 0, Math.PI / 2);
|
|
68
|
+
const lidarDomeMat = new THREE.MeshStandardMaterial({
|
|
69
|
+
color: LIDAR_DOME,
|
|
70
|
+
transparent: true,
|
|
71
|
+
opacity: 0.55,
|
|
72
|
+
roughness: 0.2,
|
|
73
|
+
metalness: 0.1,
|
|
74
|
+
emissive: LIDAR_DOME,
|
|
75
|
+
emissiveIntensity: status === 'moving' || status === 'planning' ? 0.5 : 0.15
|
|
76
|
+
});
|
|
77
|
+
const lidarDome = new THREE.Mesh(lidarDomeGeo, lidarDomeMat);
|
|
78
|
+
lidarDome.position.set(0, lidarBody.position.y + lidarRadius * 0.6, 0);
|
|
79
|
+
this.object3d.add(lidarDome);
|
|
80
|
+
// ─ Side sensors (사면 카메라) ─
|
|
81
|
+
const sensorSize = depth * 0.05;
|
|
82
|
+
const sensorGeo = new THREE.BoxGeometry(sensorSize, sensorSize, sensorSize * 1.5);
|
|
83
|
+
const sensorMat = new THREE.MeshStandardMaterial({
|
|
84
|
+
color: SENSOR_COLOR,
|
|
85
|
+
metalness: 0.7,
|
|
86
|
+
roughness: 0.3
|
|
87
|
+
});
|
|
88
|
+
const sensorPositions = [
|
|
89
|
+
[+width / 2 - sensorSize, chassis.position.y, +height / 2 - sensorSize],
|
|
90
|
+
[-width / 2 + sensorSize, chassis.position.y, +height / 2 - sensorSize],
|
|
91
|
+
[+width / 2 - sensorSize, chassis.position.y, -height / 2 + sensorSize],
|
|
92
|
+
[-width / 2 + sensorSize, chassis.position.y, -height / 2 + sensorSize]
|
|
93
|
+
];
|
|
94
|
+
for (const [x, y, z] of sensorPositions) {
|
|
95
|
+
const sensor = new THREE.Mesh(sensorGeo, sensorMat);
|
|
96
|
+
sensor.position.set(x, y, z);
|
|
97
|
+
this.object3d.add(sensor);
|
|
98
|
+
}
|
|
99
|
+
// ─ Status lamp (전방) ─
|
|
100
|
+
const lampRadius = Math.min(width, height) * 0.04;
|
|
101
|
+
const lampGeo = new THREE.SphereGeometry(lampRadius, 12, 12);
|
|
102
|
+
const lampMat = new THREE.MeshStandardMaterial({
|
|
103
|
+
color: new THREE.Color(emissiveColor),
|
|
104
|
+
emissive: new THREE.Color(emissiveColor),
|
|
105
|
+
emissiveIntensity: status === 'moving' || status === 'planning' ? 1.2 : 0.3,
|
|
106
|
+
roughness: 0.3
|
|
107
|
+
});
|
|
108
|
+
const lampL = new THREE.Mesh(lampGeo, lampMat);
|
|
109
|
+
lampL.position.set(-width * 0.25, chassis.position.y, -height / 2 + lampRadius);
|
|
110
|
+
this.object3d.add(lampL);
|
|
111
|
+
const lampR = new THREE.Mesh(lampGeo, lampMat);
|
|
112
|
+
lampR.position.set(+width * 0.25, chassis.position.y, -height / 2 + lampRadius);
|
|
113
|
+
this.object3d.add(lampR);
|
|
114
|
+
// ─ Bumpers (전후) ─
|
|
115
|
+
const bumperThickness = depth * 0.06;
|
|
116
|
+
const bumperGeo = new THREE.BoxGeometry(width * 0.96, bumperThickness, bumperThickness * 1.5);
|
|
117
|
+
const bumperMat = new THREE.MeshStandardMaterial({
|
|
118
|
+
color: BUMPER_COLOR,
|
|
119
|
+
roughness: 0.7
|
|
120
|
+
});
|
|
121
|
+
const bumperFront = new THREE.Mesh(bumperGeo, bumperMat);
|
|
122
|
+
bumperFront.position.set(0, chassis.position.y - chassisHeight * 0.3, -height / 2 + bumperThickness * 0.75);
|
|
123
|
+
this.object3d.add(bumperFront);
|
|
124
|
+
const bumperRear = new THREE.Mesh(bumperGeo, bumperMat);
|
|
125
|
+
bumperRear.position.set(0, chassis.position.y - chassisHeight * 0.3, +height / 2 - bumperThickness * 0.75);
|
|
126
|
+
this.object3d.add(bumperRear);
|
|
127
|
+
// ─ Wheels (4 corner, 단순 cylinder) ─
|
|
128
|
+
const wheelRadius = wheelHeight * 0.5;
|
|
129
|
+
const wheelWidth = width * 0.06;
|
|
130
|
+
const wheelGeo = new THREE.CylinderGeometry(wheelRadius, wheelRadius, wheelWidth, 12);
|
|
131
|
+
const wheelMat = new THREE.MeshStandardMaterial({
|
|
132
|
+
color: WHEEL_COLOR,
|
|
133
|
+
roughness: 0.8
|
|
134
|
+
});
|
|
135
|
+
const wheelOffsetX = width / 2 - wheelWidth * 0.6;
|
|
136
|
+
const wheelOffsetZ = height / 2 - wheelRadius * 1.2;
|
|
137
|
+
const wheelY = -depth / 2 + wheelRadius;
|
|
138
|
+
for (const sx of [-1, +1]) {
|
|
139
|
+
for (const sz of [-1, +1]) {
|
|
140
|
+
const wheel = new THREE.Mesh(wheelGeo, wheelMat);
|
|
141
|
+
wheel.rotation.z = Math.PI / 2;
|
|
142
|
+
wheel.position.set(sx * wheelOffsetX, wheelY, sz * wheelOffsetZ);
|
|
143
|
+
this.object3d.add(wheel);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=amr-3d.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"amr-3d.js","sourceRoot":"","sources":["../src/amr-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * AMR 3D — Autonomous Mobile Robot.\n *\n * AGV 와 구별되는 visual cue:\n * - 360° LiDAR (회전식 dome — 자율 인식의 상징)\n * - 사면 카메라 / 센서 indicator\n * - \"AUTO\" 또는 자율 status LED\n *\n * Lo-poly 베이스 — AGV 와 동일 패턴 (rounded chassis + deck + wheels) + 자율\n * 차별점만 강조.\n */\n\nimport * as THREE from 'three'\nimport { RoundedBoxGeometry } from 'three/examples/jsm/geometries/RoundedBoxGeometry.js'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nconst CHASSIS_COLOR = 0x4a4a55\nconst DECK_COLOR = 0x666677\nconst LIDAR_BODY = 0x222233\nconst LIDAR_DOME = 0x66aaff // AMR 의 LiDAR — 푸른빛 (AGV 의 회색 dome 과 구별)\nconst BUMPER_COLOR = 0xeeaa00\nconst WHEEL_COLOR = 0x202020\nconst SENSOR_COLOR = 0x445566\n\nexport class AMR3D extends RealObjectGroup {\n build() {\n super.build()\n\n const state: any = this.component.state\n const width = state.width ?? 800\n const height = state.height ?? 600\n const depth = (this as any).effectiveDepth ?? 300\n const bodyColor = state.bodyColor as string ?? '#888'\n const emissiveColor = state.lampEmissive as string ?? '#222'\n const status = state.status\n\n const chassisHeight = depth * 0.5\n const deckHeight = depth * 0.15\n const wheelHeight = depth * 0.35\n\n // ─ Chassis (rounded box) ─\n const chassisGeo = new RoundedBoxGeometry(width, chassisHeight, height, 2, depth * 0.08)\n const chassisMat = new THREE.MeshStandardMaterial({\n color: new THREE.Color(bodyColor),\n roughness: 0.5,\n metalness: 0.3\n })\n const chassis = new THREE.Mesh(chassisGeo, chassisMat)\n chassis.position.y = -depth / 2 + wheelHeight + chassisHeight / 2\n this.object3d.add(chassis)\n\n // ─ Top deck (cargo surface) ─\n const deckGeo = new RoundedBoxGeometry(width * 0.95, deckHeight, height * 0.95, 2, depth * 0.04)\n const deckMat = new THREE.MeshStandardMaterial({\n color: DECK_COLOR,\n roughness: 0.6\n })\n const deck = new THREE.Mesh(deckGeo, deckMat)\n deck.position.y = chassis.position.y + chassisHeight / 2 + deckHeight / 2\n this.object3d.add(deck)\n\n // ─ LiDAR — 360° rotating dome (AMR 의 상징) ─\n const lidarRadius = Math.min(width, height) * 0.08\n const lidarBodyGeo = new THREE.CylinderGeometry(lidarRadius, lidarRadius, lidarRadius * 1.2, 16)\n const lidarBodyMat = new THREE.MeshStandardMaterial({\n color: LIDAR_BODY,\n roughness: 0.4,\n metalness: 0.6\n })\n const lidarBody = new THREE.Mesh(lidarBodyGeo, lidarBodyMat)\n lidarBody.position.set(0, deck.position.y + deckHeight / 2 + lidarRadius * 0.6, 0)\n this.object3d.add(lidarBody)\n\n const lidarDomeGeo = new THREE.SphereGeometry(lidarRadius * 1.05, 16, 16, 0, Math.PI * 2, 0, Math.PI / 2)\n const lidarDomeMat = new THREE.MeshStandardMaterial({\n color: LIDAR_DOME,\n transparent: true,\n opacity: 0.55,\n roughness: 0.2,\n metalness: 0.1,\n emissive: LIDAR_DOME,\n emissiveIntensity: status === 'moving' || status === 'planning' ? 0.5 : 0.15\n })\n const lidarDome = new THREE.Mesh(lidarDomeGeo, lidarDomeMat)\n lidarDome.position.set(0, lidarBody.position.y + lidarRadius * 0.6, 0)\n this.object3d.add(lidarDome)\n\n // ─ Side sensors (사면 카메라) ─\n const sensorSize = depth * 0.05\n const sensorGeo = new THREE.BoxGeometry(sensorSize, sensorSize, sensorSize * 1.5)\n const sensorMat = new THREE.MeshStandardMaterial({\n color: SENSOR_COLOR,\n metalness: 0.7,\n roughness: 0.3\n })\n const sensorPositions: Array<[number, number, number]> = [\n [+width / 2 - sensorSize, chassis.position.y, +height / 2 - sensorSize],\n [-width / 2 + sensorSize, chassis.position.y, +height / 2 - sensorSize],\n [+width / 2 - sensorSize, chassis.position.y, -height / 2 + sensorSize],\n [-width / 2 + sensorSize, chassis.position.y, -height / 2 + sensorSize]\n ]\n for (const [x, y, z] of sensorPositions) {\n const sensor = new THREE.Mesh(sensorGeo, sensorMat)\n sensor.position.set(x, y, z)\n this.object3d.add(sensor)\n }\n\n // ─ Status lamp (전방) ─\n const lampRadius = Math.min(width, height) * 0.04\n const lampGeo = new THREE.SphereGeometry(lampRadius, 12, 12)\n const lampMat = new THREE.MeshStandardMaterial({\n color: new THREE.Color(emissiveColor),\n emissive: new THREE.Color(emissiveColor),\n emissiveIntensity: status === 'moving' || status === 'planning' ? 1.2 : 0.3,\n roughness: 0.3\n })\n const lampL = new THREE.Mesh(lampGeo, lampMat)\n lampL.position.set(-width * 0.25, chassis.position.y, -height / 2 + lampRadius)\n this.object3d.add(lampL)\n const lampR = new THREE.Mesh(lampGeo, lampMat)\n lampR.position.set(+width * 0.25, chassis.position.y, -height / 2 + lampRadius)\n this.object3d.add(lampR)\n\n // ─ Bumpers (전후) ─\n const bumperThickness = depth * 0.06\n const bumperGeo = new THREE.BoxGeometry(width * 0.96, bumperThickness, bumperThickness * 1.5)\n const bumperMat = new THREE.MeshStandardMaterial({\n color: BUMPER_COLOR,\n roughness: 0.7\n })\n const bumperFront = new THREE.Mesh(bumperGeo, bumperMat)\n bumperFront.position.set(0, chassis.position.y - chassisHeight * 0.3, -height / 2 + bumperThickness * 0.75)\n this.object3d.add(bumperFront)\n const bumperRear = new THREE.Mesh(bumperGeo, bumperMat)\n bumperRear.position.set(0, chassis.position.y - chassisHeight * 0.3, +height / 2 - bumperThickness * 0.75)\n this.object3d.add(bumperRear)\n\n // ─ Wheels (4 corner, 단순 cylinder) ─\n const wheelRadius = wheelHeight * 0.5\n const wheelWidth = width * 0.06\n const wheelGeo = new THREE.CylinderGeometry(wheelRadius, wheelRadius, wheelWidth, 12)\n const wheelMat = new THREE.MeshStandardMaterial({\n color: WHEEL_COLOR,\n roughness: 0.8\n })\n const wheelOffsetX = width / 2 - wheelWidth * 0.6\n const wheelOffsetZ = height / 2 - wheelRadius * 1.2\n const wheelY = -depth / 2 + wheelRadius\n for (const sx of [-1, +1]) {\n for (const sz of [-1, +1]) {\n const wheel = new THREE.Mesh(wheelGeo, wheelMat)\n wheel.rotation.z = Math.PI / 2\n wheel.position.set(sx * wheelOffsetX, wheelY, sz * wheelOffsetZ)\n this.object3d.add(wheel)\n }\n }\n }\n}\n"]}
|