@operato/scene-transport 10.0.0-beta.22
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 +26 -0
- package/README.md +55 -0
- package/dist/agv-3d.d.ts +7 -0
- package/dist/agv-3d.js +233 -0
- package/dist/agv-3d.js.map +1 -0
- package/dist/agv.d.ts +57 -0
- package/dist/agv.js +171 -0
- package/dist/agv.js.map +1 -0
- package/dist/forklift-3d.d.ts +15 -0
- package/dist/forklift-3d.js +518 -0
- package/dist/forklift-3d.js.map +1 -0
- package/dist/forklift.d.ts +58 -0
- package/dist/forklift.js +163 -0
- package/dist/forklift.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/index.d.ts +47 -0
- package/dist/templates/index.js +73 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/tugger-3d.d.ts +7 -0
- package/dist/tugger-3d.js +140 -0
- package/dist/tugger-3d.js.map +1 -0
- package/dist/tugger.d.ts +40 -0
- package/dist/tugger.js +135 -0
- package/dist/tugger.js.map +1 -0
- package/dist/worker-3d.d.ts +7 -0
- package/dist/worker-3d.js +199 -0
- package/dist/worker-3d.js.map +1 -0
- package/dist/worker.d.ts +44 -0
- package/dist/worker.js +130 -0
- package/dist/worker.js.map +1 -0
- package/icons/agv.png +0 -0
- package/icons/forklift.png +0 -0
- package/icons/tugger.png +0 -0
- package/icons/worker.png +0 -0
- package/package.json +44 -0
- package/src/agv-3d.ts +283 -0
- package/src/agv.ts +207 -0
- package/src/forklift-3d.ts +591 -0
- package/src/forklift.ts +200 -0
- package/src/index.ts +14 -0
- package/src/templates/index.ts +73 -0
- package/src/tugger-3d.ts +169 -0
- package/src/tugger.ts +169 -0
- package/src/worker-3d.ts +232 -0
- package/src/worker.ts +164 -0
- package/things-scene.config.js +5 -0
- package/translations/en.json +9 -0
- package/translations/ja.json +9 -0
- package/translations/ko.json +9 -0
- package/translations/ms.json +9 -0
- package/translations/zh.json +9 -0
- package/tsconfig.json +23 -0
- package/tsconfig.tsbuildinfo +1 -0
package/dist/tugger.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
import { RectPath, Shape, sceneComponent } from '@hatiolab/things-scene';
|
|
6
|
+
import { Legendable, Placeable } from '@operato/scene-base';
|
|
7
|
+
import { Tugger3D } from './tugger-3d.js';
|
|
8
|
+
const BODY_LEGEND = {
|
|
9
|
+
idle: '#666',
|
|
10
|
+
moving: '#777',
|
|
11
|
+
charging: '#777',
|
|
12
|
+
error: '#c66',
|
|
13
|
+
default: '#666'
|
|
14
|
+
};
|
|
15
|
+
const LAMP_EMISSIVE_LEGEND = {
|
|
16
|
+
idle: '#222222',
|
|
17
|
+
moving: '#44ff44',
|
|
18
|
+
charging: '#ffaa00',
|
|
19
|
+
error: '#ff3333',
|
|
20
|
+
default: '#222222'
|
|
21
|
+
};
|
|
22
|
+
const NATURE = {
|
|
23
|
+
mutable: false,
|
|
24
|
+
resizable: true,
|
|
25
|
+
rotatable: true,
|
|
26
|
+
properties: [
|
|
27
|
+
{
|
|
28
|
+
type: 'select',
|
|
29
|
+
label: 'status',
|
|
30
|
+
name: 'status',
|
|
31
|
+
property: {
|
|
32
|
+
options: [
|
|
33
|
+
{ display: 'Idle', value: 'idle' },
|
|
34
|
+
{ display: 'Moving', value: 'moving' },
|
|
35
|
+
{ display: 'Charging', value: 'charging' },
|
|
36
|
+
{ display: 'Error', value: 'error' }
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: 'number',
|
|
42
|
+
label: 'battery',
|
|
43
|
+
name: 'battery',
|
|
44
|
+
placeholder: '0..1'
|
|
45
|
+
}
|
|
46
|
+
],
|
|
47
|
+
help: 'scene/component/tugger'
|
|
48
|
+
};
|
|
49
|
+
const Base = Legendable(Placeable(RectPath(Shape)));
|
|
50
|
+
/**
|
|
51
|
+
* Tugger has no cargo deck — just a powered tractor with a hitch. Trailers
|
|
52
|
+
* are separate scene components and are linked by data binding (a
|
|
53
|
+
* `currentTrailer` data field) rather than scene-tree containment, since
|
|
54
|
+
* trailers swap dynamically and don't share the tractor's transform.
|
|
55
|
+
*
|
|
56
|
+
* Default depth is shorter than a payload AGV — there's no operation surface
|
|
57
|
+
* to align to. ~600mm is enough for the motor housing + control panel.
|
|
58
|
+
*/
|
|
59
|
+
let Tugger = class Tugger extends Base {
|
|
60
|
+
static legends = {
|
|
61
|
+
bodyColor: { from: 'status', legend: BODY_LEGEND },
|
|
62
|
+
lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }
|
|
63
|
+
};
|
|
64
|
+
static placement = 'floor';
|
|
65
|
+
static align = 'bottom';
|
|
66
|
+
static defaultDepth = 600;
|
|
67
|
+
get nature() {
|
|
68
|
+
return NATURE;
|
|
69
|
+
}
|
|
70
|
+
get anchors() {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 2D — render() draws the rounded body silhouette (auto-filled with
|
|
75
|
+
* bodyColor); postrender() adds the hitch arm + LiDAR + status lamp.
|
|
76
|
+
*/
|
|
77
|
+
render(ctx) {
|
|
78
|
+
const { width, height, left, top } = this.state;
|
|
79
|
+
const radius = Math.min(width, height) * 0.18;
|
|
80
|
+
ctx.beginPath();
|
|
81
|
+
ctx.roundRect(left, top + height * 0.10, width, height * 0.80, radius);
|
|
82
|
+
}
|
|
83
|
+
postrender(ctx) {
|
|
84
|
+
super.postrender?.(ctx);
|
|
85
|
+
const { width, height, left, top } = this.state;
|
|
86
|
+
const cx = left + width / 2;
|
|
87
|
+
const accentColor = this.state.lampEmissive || '#44ff44';
|
|
88
|
+
ctx.save();
|
|
89
|
+
// Hitch arm + coupler at rear (top in top-down convention)
|
|
90
|
+
ctx.fillStyle = '#222233';
|
|
91
|
+
ctx.strokeStyle = '#111';
|
|
92
|
+
ctx.lineWidth = 1;
|
|
93
|
+
const hitchW = width * 0.12;
|
|
94
|
+
const hitchH = height * 0.10;
|
|
95
|
+
ctx.fillRect(cx - hitchW / 2, top, hitchW, hitchH);
|
|
96
|
+
ctx.strokeRect(cx - hitchW / 2, top, hitchW, hitchH);
|
|
97
|
+
// Coupler ball
|
|
98
|
+
const ballR = Math.min(width, height) * 0.04;
|
|
99
|
+
ctx.fillStyle = '#444455';
|
|
100
|
+
ctx.beginPath();
|
|
101
|
+
ctx.ellipse(cx, top + ballR, ballR, ballR, 0, 0, Math.PI * 2);
|
|
102
|
+
ctx.fill();
|
|
103
|
+
ctx.stroke();
|
|
104
|
+
// LiDAR sensor at front (bottom)
|
|
105
|
+
const lidarR = Math.min(width, height) * 0.07;
|
|
106
|
+
ctx.fillStyle = '#222233';
|
|
107
|
+
ctx.beginPath();
|
|
108
|
+
ctx.ellipse(cx, top + height * 0.85, lidarR, lidarR, 0, 0, Math.PI * 2);
|
|
109
|
+
ctx.fill();
|
|
110
|
+
ctx.stroke();
|
|
111
|
+
ctx.fillStyle = accentColor;
|
|
112
|
+
ctx.beginPath();
|
|
113
|
+
ctx.ellipse(cx, top + height * 0.85, lidarR * 0.4, lidarR * 0.4, 0, 0, Math.PI * 2);
|
|
114
|
+
ctx.fill();
|
|
115
|
+
// Status lamp on top of control panel (center)
|
|
116
|
+
const lampR = Math.min(width, height) * 0.06;
|
|
117
|
+
ctx.fillStyle = accentColor;
|
|
118
|
+
ctx.beginPath();
|
|
119
|
+
ctx.ellipse(cx, top + height * 0.45, lampR, lampR, 0, 0, Math.PI * 2);
|
|
120
|
+
ctx.fill();
|
|
121
|
+
ctx.stroke();
|
|
122
|
+
ctx.restore();
|
|
123
|
+
}
|
|
124
|
+
get fillStyle() {
|
|
125
|
+
return this.state.bodyColor || '#666';
|
|
126
|
+
}
|
|
127
|
+
buildRealObject() {
|
|
128
|
+
return new Tugger3D(this);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
Tugger = __decorate([
|
|
132
|
+
sceneComponent('tugger')
|
|
133
|
+
], Tugger);
|
|
134
|
+
export default Tugger;
|
|
135
|
+
//# sourceMappingURL=tugger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tugger.js","sourceRoot":"","sources":["../src/tugger.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA0C,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAChH,OAAO,EACL,UAAU,EACV,SAAS,EAKV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAezC,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,MAAM;IACd,QAAQ,EAAE,MAAM;IAChB,KAAK,EAAE,MAAM;IACb,OAAO,EAAE,MAAM;CAChB,CAAA;AAED,MAAM,oBAAoB,GAAG;IAC3B,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,SAAS;IACjB,QAAQ,EAAE,SAAS;IACnB,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,SAAS;CACnB,CAAA;AAED,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,UAAU,EAAE;QACV;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE;gBACR,OAAO,EAAE;oBACP,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;oBAClC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE;oBACtC,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE;oBAC1C,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;iBACrC;aACF;SACF;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,SAAS;YAChB,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,MAAM;SACpB;KACF;IACD,IAAI,EAAE,wBAAwB;CAC/B,CAAA;AAED,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAgC,CAAA;AAElF;;;;;;;;GAQG;AAEY,IAAM,MAAM,GAAZ,MAAM,MAAO,SAAQ,IAAI;IACtC,MAAM,CAAC,OAAO,GAAkC;QAC9C,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE;QAClD,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,oBAAoB,EAAE;KAC/D,CAAA;IAED,MAAM,CAAC,SAAS,GAAuB,OAAO,CAAA;IAC9C,MAAM,CAAC,KAAK,GAAc,QAAQ,CAAA;IAClC,MAAM,CAAC,YAAY,GAAG,GAAG,CAAA;IAEzB,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QAC7C,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,CAAA;IACxE,CAAC;IAED,UAAU,CAAC,GAA6B;QACtC,KAAK,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAA;QAEvB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,MAAM,EAAE,GAAG,IAAI,GAAG,KAAK,GAAG,CAAC,CAAA;QAC3B,MAAM,WAAW,GAAI,IAAI,CAAC,KAAK,CAAC,YAAuB,IAAI,SAAS,CAAA;QAEpE,GAAG,CAAC,IAAI,EAAE,CAAA;QAEV,2DAA2D;QAC3D,GAAG,CAAC,SAAS,GAAG,SAAS,CAAA;QACzB,GAAG,CAAC,WAAW,GAAG,MAAM,CAAA;QACxB,GAAG,CAAC,SAAS,GAAG,CAAC,CAAA;QACjB,MAAM,MAAM,GAAG,KAAK,GAAG,IAAI,CAAA;QAC3B,MAAM,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;QAC5B,GAAG,CAAC,QAAQ,CAAC,EAAE,GAAG,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;QAClD,GAAG,CAAC,UAAU,CAAC,EAAE,GAAG,MAAM,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;QACpD,eAAe;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QAC5C,GAAG,CAAC,SAAS,GAAG,SAAS,CAAA;QACzB,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QAC7D,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,MAAM,EAAE,CAAA;QAEZ,iCAAiC;QACjC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QAC7C,GAAG,CAAC,SAAS,GAAG,SAAS,CAAA;QACzB,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QACvE,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,MAAM,EAAE,CAAA;QACZ,GAAG,CAAC,SAAS,GAAG,WAAW,CAAA;QAC3B,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,MAAM,GAAG,GAAG,EAAE,MAAM,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QACnF,GAAG,CAAC,IAAI,EAAE,CAAA;QAEV,+CAA+C;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,IAAI,CAAA;QAC5C,GAAG,CAAC,SAAS,GAAG,WAAW,CAAA;QAC3B,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QACrE,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,MAAM,EAAE,CAAA;QAEZ,GAAG,CAAC,OAAO,EAAE,CAAA;IACf,CAAC;IAED,IAAI,SAAS;QACX,OAAQ,IAAI,CAAC,KAAK,CAAC,SAAoB,IAAI,MAAM,CAAA;IACnD,CAAC;IAED,eAAe;QACb,OAAO,IAAI,QAAQ,CAAC,IAAW,CAAC,CAAA;IAClC,CAAC;;AAnFkB,MAAM;IAD1B,cAAc,CAAC,QAAQ,CAAC;GACJ,MAAM,CAoF1B;eApFoB,MAAM","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, RealObject, RectPath, Shape, sceneComponent } from '@hatiolab/things-scene'\nimport {\n Legendable,\n Placeable,\n type Alignment,\n type Heights,\n type LegendBinding,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Tugger3D } from './tugger-3d.js'\n\nimport type { AgvStatus } from './agv.js'\n\n/**\n * Tugger — a towing AGV. Pulls trailers / carts behind it; has no cargo deck\n * of its own. Common in mail sorting, automotive line-side delivery, hospital\n * supply transport.\n *\n * Status enum is reused from {@link AgvStatus} — the tugger and the unit-load\n * AGV share the same operating-state semantics. The structural difference\n * (cargo deck vs hitch) is in geometry, not state.\n */\nexport type TuggerStatus = AgvStatus\n\nconst BODY_LEGEND = {\n idle: '#666',\n moving: '#777',\n charging: '#777',\n error: '#c66',\n default: '#666'\n}\n\nconst LAMP_EMISSIVE_LEGEND = {\n idle: '#222222',\n moving: '#44ff44',\n charging: '#ffaa00',\n error: '#ff3333',\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 help: 'scene/component/tugger'\n}\n\nconst Base = Legendable(Placeable(RectPath(Shape))) as unknown as typeof Component\n\n/**\n * Tugger has no cargo deck — just a powered tractor with a hitch. Trailers\n * are separate scene components and are linked by data binding (a\n * `currentTrailer` data field) rather than scene-tree containment, since\n * trailers swap dynamically and don't share the tractor's transform.\n *\n * Default depth is shorter than a payload AGV — there's no operation surface\n * to align to. ~600mm is enough for the motor housing + control panel.\n */\n@sceneComponent('tugger')\nexport default class Tugger extends Base {\n static legends: Record<string, LegendBinding> = {\n bodyColor: { from: 'status', legend: BODY_LEGEND },\n lampEmissive: { from: 'status', legend: LAMP_EMISSIVE_LEGEND }\n }\n\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = 600\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — render() draws the rounded body silhouette (auto-filled with\n * bodyColor); postrender() adds the hitch arm + LiDAR + status lamp.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n const radius = Math.min(width, height) * 0.18\n ctx.beginPath()\n ctx.roundRect(left, top + height * 0.10, width, height * 0.80, radius)\n }\n\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 accentColor = (this.state.lampEmissive as string) || '#44ff44'\n\n ctx.save()\n\n // Hitch arm + coupler at rear (top in top-down convention)\n ctx.fillStyle = '#222233'\n ctx.strokeStyle = '#111'\n ctx.lineWidth = 1\n const hitchW = width * 0.12\n const hitchH = height * 0.10\n ctx.fillRect(cx - hitchW / 2, top, hitchW, hitchH)\n ctx.strokeRect(cx - hitchW / 2, top, hitchW, hitchH)\n // Coupler ball\n const ballR = Math.min(width, height) * 0.04\n ctx.fillStyle = '#444455'\n ctx.beginPath()\n ctx.ellipse(cx, top + ballR, ballR, ballR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // LiDAR sensor at front (bottom)\n const lidarR = Math.min(width, height) * 0.07\n ctx.fillStyle = '#222233'\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.85, lidarR, lidarR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.85, lidarR * 0.4, lidarR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n // Status lamp on top of control panel (center)\n const lampR = Math.min(width, height) * 0.06\n ctx.fillStyle = accentColor\n ctx.beginPath()\n ctx.ellipse(cx, top + height * 0.45, lampR, lampR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.bodyColor as string) || '#666'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Tugger3D(this as any)\n }\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RealObjectGroup } from '@hatiolab/things-scene';
|
|
2
|
+
export declare class Worker3D extends RealObjectGroup {
|
|
3
|
+
build(): void;
|
|
4
|
+
updateDimension(): void;
|
|
5
|
+
onchange(after: Record<string, unknown>, before: Record<string, unknown>): void;
|
|
6
|
+
updateAlpha(): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* Worker 3D — a stylized humanoid figure built from primitives.
|
|
5
|
+
*
|
|
6
|
+
* LO-POLY but recognizably industrial-worker. The signature parts:
|
|
7
|
+
*
|
|
8
|
+
* - boots (two flat-top boxes — workboots, not sneakers)
|
|
9
|
+
* - legs (two cylinders, slight stance — not perfectly parallel)
|
|
10
|
+
* - belt with tool pouch
|
|
11
|
+
* - torso + hi-vis vest (cylinder, vest color from legend)
|
|
12
|
+
* - reflective stripes on vest (two horizontal bands — ANSI 107 hi-vis)
|
|
13
|
+
* - shoulders + slight A-pose arms (arms angled slightly away from body)
|
|
14
|
+
* - hands (small spheres at arm ends)
|
|
15
|
+
* - badge / nametag hint on chest
|
|
16
|
+
* - neck (small cylinder between torso and head)
|
|
17
|
+
* - head (skin-tone sphere, smaller than minimal anatomy)
|
|
18
|
+
* - full helmet (not just top hemisphere — covers the whole upper head)
|
|
19
|
+
* - chinstrap hint
|
|
20
|
+
*
|
|
21
|
+
* Heights are proportioned against `depth` (= worker height ~1700mm).
|
|
22
|
+
*/
|
|
23
|
+
import * as THREE from 'three';
|
|
24
|
+
import { RealObjectGroup } from '@hatiolab/things-scene';
|
|
25
|
+
const SKIN_COLOR = 0xc89878;
|
|
26
|
+
const PANTS_COLOR = 0x2a3a55;
|
|
27
|
+
const BOOT_COLOR = 0x141414;
|
|
28
|
+
const BELT_COLOR = 0x1a1a1a;
|
|
29
|
+
const POUCH_COLOR = 0x2c1810;
|
|
30
|
+
const REFLECTIVE_COLOR = 0xeaeaea;
|
|
31
|
+
const BADGE_COLOR = 0xeeeeee;
|
|
32
|
+
export class Worker3D extends RealObjectGroup {
|
|
33
|
+
build() {
|
|
34
|
+
super.build();
|
|
35
|
+
const { width, height, depth = 1700 } = this.component.state;
|
|
36
|
+
const vestColor = this.component.state.vestColor || '#FFD700';
|
|
37
|
+
const helmetEmissive = this.component.state.helmetEmissive || '#222222';
|
|
38
|
+
const status = this.component.state.status;
|
|
39
|
+
const helmetIntensity = status && status !== 'idle' ? 1.2 : 0.0;
|
|
40
|
+
// Body part heights (proportional to total height = depth)
|
|
41
|
+
const bootH = depth * 0.04;
|
|
42
|
+
const legH = depth * 0.42;
|
|
43
|
+
const beltH = depth * 0.025;
|
|
44
|
+
const torsoH = depth * 0.30;
|
|
45
|
+
const neckH = depth * 0.025;
|
|
46
|
+
const headR = depth * 0.06;
|
|
47
|
+
const helmetR = headR * 1.10;
|
|
48
|
+
const helmetH = headR * 1.4;
|
|
49
|
+
let yCursor = -depth / 2;
|
|
50
|
+
// ── Boots (two flat-top boxes — work boots) ──────────────────────
|
|
51
|
+
const bootW = depth * 0.06;
|
|
52
|
+
const bootD = depth * 0.13;
|
|
53
|
+
const bootMaterial = new THREE.MeshStandardMaterial({ color: BOOT_COLOR, roughness: 0.85 });
|
|
54
|
+
for (const xSign of [-1, 1]) {
|
|
55
|
+
const bootGeo = new THREE.BoxGeometry(bootW, bootH, bootD);
|
|
56
|
+
const bootMesh = new THREE.Mesh(bootGeo, bootMaterial);
|
|
57
|
+
bootMesh.position.set(xSign * bootW * 0.7, yCursor + bootH / 2, bootD * 0.10);
|
|
58
|
+
bootMesh.castShadow = true;
|
|
59
|
+
this.object3d.add(bootMesh);
|
|
60
|
+
}
|
|
61
|
+
yCursor += bootH;
|
|
62
|
+
// ── Legs (two cylinders, slight stance) ──────────────────────────
|
|
63
|
+
const legR = depth * 0.038;
|
|
64
|
+
const legSpacing = legR * 1.3;
|
|
65
|
+
const pantsMaterial = new THREE.MeshStandardMaterial({ color: PANTS_COLOR, roughness: 0.7 });
|
|
66
|
+
for (const xSign of [-1, 1]) {
|
|
67
|
+
const legGeo = new THREE.CylinderGeometry(legR, legR * 1.05, legH, 20);
|
|
68
|
+
const legMesh = new THREE.Mesh(legGeo, pantsMaterial);
|
|
69
|
+
legMesh.position.set(xSign * legSpacing, yCursor + legH / 2, 0);
|
|
70
|
+
legMesh.castShadow = true;
|
|
71
|
+
this.object3d.add(legMesh);
|
|
72
|
+
}
|
|
73
|
+
yCursor += legH;
|
|
74
|
+
// ── Belt + tool pouch ────────────────────────────────────────────
|
|
75
|
+
const torsoR = depth * 0.085;
|
|
76
|
+
const beltGeo = new THREE.CylinderGeometry(torsoR * 1.05, torsoR * 1.05, beltH, 24);
|
|
77
|
+
const beltMaterial = new THREE.MeshStandardMaterial({ color: BELT_COLOR, roughness: 0.6 });
|
|
78
|
+
const beltMesh = new THREE.Mesh(beltGeo, beltMaterial);
|
|
79
|
+
beltMesh.position.set(0, yCursor + beltH / 2, 0);
|
|
80
|
+
this.object3d.add(beltMesh);
|
|
81
|
+
// Tool pouch on the right side
|
|
82
|
+
const pouchW = torsoR * 0.7;
|
|
83
|
+
const pouchH = beltH * 2.5;
|
|
84
|
+
const pouchD = torsoR * 0.5;
|
|
85
|
+
const pouchGeo = new THREE.BoxGeometry(pouchW, pouchH, pouchD);
|
|
86
|
+
const pouchMesh = new THREE.Mesh(pouchGeo, new THREE.MeshStandardMaterial({ color: POUCH_COLOR, roughness: 0.8 }));
|
|
87
|
+
pouchMesh.position.set(torsoR * 1.0, yCursor + beltH / 2 - pouchH * 0.2, 0);
|
|
88
|
+
this.object3d.add(pouchMesh);
|
|
89
|
+
yCursor += beltH;
|
|
90
|
+
// ── Torso + hi-vis vest ──────────────────────────────────────────
|
|
91
|
+
const vestMaterial = new THREE.MeshStandardMaterial({
|
|
92
|
+
color: vestColor,
|
|
93
|
+
metalness: 0.05,
|
|
94
|
+
roughness: 0.6
|
|
95
|
+
});
|
|
96
|
+
const torsoGeo = new THREE.CylinderGeometry(torsoR, torsoR * 0.92, torsoH, 24);
|
|
97
|
+
const torsoMesh = new THREE.Mesh(torsoGeo, vestMaterial);
|
|
98
|
+
torsoMesh.position.set(0, yCursor + torsoH / 2, 0);
|
|
99
|
+
torsoMesh.castShadow = true;
|
|
100
|
+
this.object3d.add(torsoMesh);
|
|
101
|
+
// Reflective stripes on vest (ANSI 107 — two horizontal bands)
|
|
102
|
+
const stripeMaterial = new THREE.MeshStandardMaterial({
|
|
103
|
+
color: REFLECTIVE_COLOR,
|
|
104
|
+
metalness: 0.4,
|
|
105
|
+
roughness: 0.2
|
|
106
|
+
});
|
|
107
|
+
const stripeH = torsoH * 0.07;
|
|
108
|
+
for (const yFrac of [0.30, 0.55]) {
|
|
109
|
+
const stripeGeo = new THREE.CylinderGeometry(torsoR * 1.005, torsoR * 0.93, stripeH, 24);
|
|
110
|
+
const stripeMesh = new THREE.Mesh(stripeGeo, stripeMaterial);
|
|
111
|
+
stripeMesh.position.set(0, yCursor + torsoH * yFrac, 0);
|
|
112
|
+
this.object3d.add(stripeMesh);
|
|
113
|
+
}
|
|
114
|
+
// Badge / nametag on chest
|
|
115
|
+
const badgeGeo = new THREE.BoxGeometry(torsoR * 0.55, torsoR * 0.35, torsoR * 0.05);
|
|
116
|
+
const badgeMesh = new THREE.Mesh(badgeGeo, new THREE.MeshStandardMaterial({ color: BADGE_COLOR, roughness: 0.5 }));
|
|
117
|
+
badgeMesh.position.set(-torsoR * 0.45, yCursor + torsoH * 0.78, torsoR * 0.95);
|
|
118
|
+
this.object3d.add(badgeMesh);
|
|
119
|
+
// ── Arms + hands (slight A-pose, hands at sides) ─────────────────
|
|
120
|
+
const skinMaterial = new THREE.MeshStandardMaterial({ color: SKIN_COLOR, roughness: 0.7 });
|
|
121
|
+
const armR = depth * 0.026;
|
|
122
|
+
const armH = torsoH * 1.05;
|
|
123
|
+
// Arms angled outward by ~10 degrees
|
|
124
|
+
const armAngle = Math.PI * 0.05;
|
|
125
|
+
for (const xSign of [-1, 1]) {
|
|
126
|
+
const armGeo = new THREE.CylinderGeometry(armR, armR, armH, 16);
|
|
127
|
+
armGeo.rotateZ(xSign * -armAngle);
|
|
128
|
+
// After rotation, the arm's center is offset slightly outward and down
|
|
129
|
+
const armMesh = new THREE.Mesh(armGeo, skinMaterial);
|
|
130
|
+
const armCx = xSign * (torsoR + armR + armH * 0.5 * Math.sin(armAngle));
|
|
131
|
+
const armCy = yCursor + torsoH * 0.85 - armH / 2;
|
|
132
|
+
armMesh.position.set(armCx, armCy, 0);
|
|
133
|
+
armMesh.castShadow = true;
|
|
134
|
+
this.object3d.add(armMesh);
|
|
135
|
+
// Hand (small sphere at arm end)
|
|
136
|
+
const handR = armR * 1.2;
|
|
137
|
+
const handGeo = new THREE.SphereGeometry(handR, 24, 8);
|
|
138
|
+
const handMesh = new THREE.Mesh(handGeo, skinMaterial);
|
|
139
|
+
const handCx = armCx + xSign * (armH / 2) * Math.sin(armAngle);
|
|
140
|
+
const handCy = armCy - armH / 2 - handR * 0.5;
|
|
141
|
+
handMesh.position.set(handCx, handCy, 0);
|
|
142
|
+
this.object3d.add(handMesh);
|
|
143
|
+
}
|
|
144
|
+
yCursor += torsoH;
|
|
145
|
+
// ── Neck ─────────────────────────────────────────────────────────
|
|
146
|
+
const neckR = headR * 0.55;
|
|
147
|
+
const neckGeo = new THREE.CylinderGeometry(neckR, neckR * 1.15, neckH, 16);
|
|
148
|
+
const neckMesh = new THREE.Mesh(neckGeo, skinMaterial);
|
|
149
|
+
neckMesh.position.set(0, yCursor + neckH / 2, 0);
|
|
150
|
+
this.object3d.add(neckMesh);
|
|
151
|
+
yCursor += neckH;
|
|
152
|
+
// ── Head ─────────────────────────────────────────────────────────
|
|
153
|
+
const headGeo = new THREE.SphereGeometry(headR, 20, 12);
|
|
154
|
+
const headMesh = new THREE.Mesh(headGeo, skinMaterial);
|
|
155
|
+
headMesh.position.set(0, yCursor + headR, 0);
|
|
156
|
+
headMesh.castShadow = true;
|
|
157
|
+
this.object3d.add(headMesh);
|
|
158
|
+
// ── Helmet (full hard hat — top sphere + brim) ───────────────────
|
|
159
|
+
// Top: half-sphere covering upper head (3/5 of sphere)
|
|
160
|
+
const helmetMaterial = new THREE.MeshStandardMaterial({
|
|
161
|
+
color: vestColor, // matches vest base
|
|
162
|
+
emissive: helmetEmissive,
|
|
163
|
+
emissiveIntensity: helmetIntensity,
|
|
164
|
+
metalness: 0.1,
|
|
165
|
+
roughness: 0.45
|
|
166
|
+
});
|
|
167
|
+
const helmetTopGeo = new THREE.SphereGeometry(helmetR, 24, 16, 0, Math.PI * 2, 0, Math.PI * 0.6);
|
|
168
|
+
const helmetTopMesh = new THREE.Mesh(helmetTopGeo, helmetMaterial);
|
|
169
|
+
helmetTopMesh.position.set(0, yCursor + headR * 0.7, 0);
|
|
170
|
+
helmetTopMesh.castShadow = true;
|
|
171
|
+
this.object3d.add(helmetTopMesh);
|
|
172
|
+
// Brim: thin disk in front
|
|
173
|
+
const brimGeo = new THREE.CylinderGeometry(helmetR * 1.05, helmetR * 1.05, helmetR * 0.12, 16, 1, false, -Math.PI * 0.6, Math.PI * 1.2);
|
|
174
|
+
const brimMesh = new THREE.Mesh(brimGeo, helmetMaterial);
|
|
175
|
+
brimMesh.position.set(0, yCursor + headR * 0.85, headR * 0.1);
|
|
176
|
+
this.object3d.add(brimMesh);
|
|
177
|
+
// Chinstrap hint (thin ring around lower head)
|
|
178
|
+
const strapGeo = new THREE.TorusGeometry(headR * 0.95, headR * 0.03, 6, 16);
|
|
179
|
+
strapGeo.rotateX(Math.PI / 2);
|
|
180
|
+
const strapMesh = new THREE.Mesh(strapGeo, new THREE.MeshStandardMaterial({ color: BELT_COLOR, roughness: 0.6 }));
|
|
181
|
+
strapMesh.position.set(0, yCursor + headR * 0.6, 0);
|
|
182
|
+
this.object3d.add(strapMesh);
|
|
183
|
+
}
|
|
184
|
+
updateDimension() { }
|
|
185
|
+
onchange(after, before) {
|
|
186
|
+
if ('status' in after ||
|
|
187
|
+
'vestColor' in after ||
|
|
188
|
+
'helmetEmissive' in after ||
|
|
189
|
+
'width' in after ||
|
|
190
|
+
'height' in after ||
|
|
191
|
+
'depth' in after) {
|
|
192
|
+
this.update();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
super.onchange(after, before);
|
|
196
|
+
}
|
|
197
|
+
updateAlpha() { }
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=worker-3d.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-3d.js","sourceRoot":"","sources":["../src/worker-3d.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * Worker 3D — a stylized humanoid figure built from primitives.\n *\n * LO-POLY but recognizably industrial-worker. The signature parts:\n *\n * - boots (two flat-top boxes — workboots, not sneakers)\n * - legs (two cylinders, slight stance — not perfectly parallel)\n * - belt with tool pouch\n * - torso + hi-vis vest (cylinder, vest color from legend)\n * - reflective stripes on vest (two horizontal bands — ANSI 107 hi-vis)\n * - shoulders + slight A-pose arms (arms angled slightly away from body)\n * - hands (small spheres at arm ends)\n * - badge / nametag hint on chest\n * - neck (small cylinder between torso and head)\n * - head (skin-tone sphere, smaller than minimal anatomy)\n * - full helmet (not just top hemisphere — covers the whole upper head)\n * - chinstrap hint\n *\n * Heights are proportioned against `depth` (= worker height ~1700mm).\n */\n\nimport * as THREE from 'three'\nimport { RealObjectGroup } from '@hatiolab/things-scene'\n\nconst SKIN_COLOR = 0xc89878\nconst PANTS_COLOR = 0x2a3a55\nconst BOOT_COLOR = 0x141414\nconst BELT_COLOR = 0x1a1a1a\nconst POUCH_COLOR = 0x2c1810\nconst REFLECTIVE_COLOR = 0xeaeaea\nconst BADGE_COLOR = 0xeeeeee\n\nexport class Worker3D extends RealObjectGroup {\n build() {\n super.build()\n\n const { width, height, depth = 1700 } = this.component.state\n const vestColor = (this.component.state.vestColor as string) || '#FFD700'\n const helmetEmissive = (this.component.state.helmetEmissive as string) || '#222222'\n const status = this.component.state.status\n const helmetIntensity = status && status !== 'idle' ? 1.2 : 0.0\n\n // Body part heights (proportional to total height = depth)\n const bootH = depth * 0.04\n const legH = depth * 0.42\n const beltH = depth * 0.025\n const torsoH = depth * 0.30\n const neckH = depth * 0.025\n const headR = depth * 0.06\n const helmetR = headR * 1.10\n const helmetH = headR * 1.4\n\n let yCursor = -depth / 2\n\n // ── Boots (two flat-top boxes — work boots) ──────────────────────\n const bootW = depth * 0.06\n const bootD = depth * 0.13\n const bootMaterial = new THREE.MeshStandardMaterial({ color: BOOT_COLOR, roughness: 0.85 })\n for (const xSign of [-1, 1]) {\n const bootGeo = new THREE.BoxGeometry(bootW, bootH, bootD)\n const bootMesh = new THREE.Mesh(bootGeo, bootMaterial)\n bootMesh.position.set(xSign * bootW * 0.7, yCursor + bootH / 2, bootD * 0.10)\n bootMesh.castShadow = true\n this.object3d.add(bootMesh)\n }\n yCursor += bootH\n\n // ── Legs (two cylinders, slight stance) ──────────────────────────\n const legR = depth * 0.038\n const legSpacing = legR * 1.3\n const pantsMaterial = new THREE.MeshStandardMaterial({ color: PANTS_COLOR, roughness: 0.7 })\n for (const xSign of [-1, 1]) {\n const legGeo = new THREE.CylinderGeometry(legR, legR * 1.05, legH, 20)\n const legMesh = new THREE.Mesh(legGeo, pantsMaterial)\n legMesh.position.set(xSign * legSpacing, yCursor + legH / 2, 0)\n legMesh.castShadow = true\n this.object3d.add(legMesh)\n }\n yCursor += legH\n\n // ── Belt + tool pouch ────────────────────────────────────────────\n const torsoR = depth * 0.085\n const beltGeo = new THREE.CylinderGeometry(torsoR * 1.05, torsoR * 1.05, beltH, 24)\n const beltMaterial = new THREE.MeshStandardMaterial({ color: BELT_COLOR, roughness: 0.6 })\n const beltMesh = new THREE.Mesh(beltGeo, beltMaterial)\n beltMesh.position.set(0, yCursor + beltH / 2, 0)\n this.object3d.add(beltMesh)\n\n // Tool pouch on the right side\n const pouchW = torsoR * 0.7\n const pouchH = beltH * 2.5\n const pouchD = torsoR * 0.5\n const pouchGeo = new THREE.BoxGeometry(pouchW, pouchH, pouchD)\n const pouchMesh = new THREE.Mesh(\n pouchGeo,\n new THREE.MeshStandardMaterial({ color: POUCH_COLOR, roughness: 0.8 })\n )\n pouchMesh.position.set(torsoR * 1.0, yCursor + beltH / 2 - pouchH * 0.2, 0)\n this.object3d.add(pouchMesh)\n yCursor += beltH\n\n // ── Torso + hi-vis vest ──────────────────────────────────────────\n const vestMaterial = new THREE.MeshStandardMaterial({\n color: vestColor,\n metalness: 0.05,\n roughness: 0.6\n })\n const torsoGeo = new THREE.CylinderGeometry(torsoR, torsoR * 0.92, torsoH, 24)\n const torsoMesh = new THREE.Mesh(torsoGeo, vestMaterial)\n torsoMesh.position.set(0, yCursor + torsoH / 2, 0)\n torsoMesh.castShadow = true\n this.object3d.add(torsoMesh)\n\n // Reflective stripes on vest (ANSI 107 — two horizontal bands)\n const stripeMaterial = new THREE.MeshStandardMaterial({\n color: REFLECTIVE_COLOR,\n metalness: 0.4,\n roughness: 0.2\n })\n const stripeH = torsoH * 0.07\n for (const yFrac of [0.30, 0.55]) {\n const stripeGeo = new THREE.CylinderGeometry(torsoR * 1.005, torsoR * 0.93, stripeH, 24)\n const stripeMesh = new THREE.Mesh(stripeGeo, stripeMaterial)\n stripeMesh.position.set(0, yCursor + torsoH * yFrac, 0)\n this.object3d.add(stripeMesh)\n }\n\n // Badge / nametag on chest\n const badgeGeo = new THREE.BoxGeometry(torsoR * 0.55, torsoR * 0.35, torsoR * 0.05)\n const badgeMesh = new THREE.Mesh(\n badgeGeo,\n new THREE.MeshStandardMaterial({ color: BADGE_COLOR, roughness: 0.5 })\n )\n badgeMesh.position.set(-torsoR * 0.45, yCursor + torsoH * 0.78, torsoR * 0.95)\n this.object3d.add(badgeMesh)\n\n // ── Arms + hands (slight A-pose, hands at sides) ─────────────────\n const skinMaterial = new THREE.MeshStandardMaterial({ color: SKIN_COLOR, roughness: 0.7 })\n const armR = depth * 0.026\n const armH = torsoH * 1.05\n // Arms angled outward by ~10 degrees\n const armAngle = Math.PI * 0.05\n for (const xSign of [-1, 1]) {\n const armGeo = new THREE.CylinderGeometry(armR, armR, armH, 16)\n armGeo.rotateZ(xSign * -armAngle)\n // After rotation, the arm's center is offset slightly outward and down\n const armMesh = new THREE.Mesh(armGeo, skinMaterial)\n const armCx = xSign * (torsoR + armR + armH * 0.5 * Math.sin(armAngle))\n const armCy = yCursor + torsoH * 0.85 - armH / 2\n armMesh.position.set(armCx, armCy, 0)\n armMesh.castShadow = true\n this.object3d.add(armMesh)\n\n // Hand (small sphere at arm end)\n const handR = armR * 1.2\n const handGeo = new THREE.SphereGeometry(handR, 24, 8)\n const handMesh = new THREE.Mesh(handGeo, skinMaterial)\n const handCx = armCx + xSign * (armH / 2) * Math.sin(armAngle)\n const handCy = armCy - armH / 2 - handR * 0.5\n handMesh.position.set(handCx, handCy, 0)\n this.object3d.add(handMesh)\n }\n yCursor += torsoH\n\n // ── Neck ─────────────────────────────────────────────────────────\n const neckR = headR * 0.55\n const neckGeo = new THREE.CylinderGeometry(neckR, neckR * 1.15, neckH, 16)\n const neckMesh = new THREE.Mesh(neckGeo, skinMaterial)\n neckMesh.position.set(0, yCursor + neckH / 2, 0)\n this.object3d.add(neckMesh)\n yCursor += neckH\n\n // ── Head ─────────────────────────────────────────────────────────\n const headGeo = new THREE.SphereGeometry(headR, 20, 12)\n const headMesh = new THREE.Mesh(headGeo, skinMaterial)\n headMesh.position.set(0, yCursor + headR, 0)\n headMesh.castShadow = true\n this.object3d.add(headMesh)\n\n // ── Helmet (full hard hat — top sphere + brim) ───────────────────\n // Top: half-sphere covering upper head (3/5 of sphere)\n const helmetMaterial = new THREE.MeshStandardMaterial({\n color: vestColor, // matches vest base\n emissive: helmetEmissive,\n emissiveIntensity: helmetIntensity,\n metalness: 0.1,\n roughness: 0.45\n })\n const helmetTopGeo = new THREE.SphereGeometry(helmetR, 24, 16, 0, Math.PI * 2, 0, Math.PI * 0.6)\n const helmetTopMesh = new THREE.Mesh(helmetTopGeo, helmetMaterial)\n helmetTopMesh.position.set(0, yCursor + headR * 0.7, 0)\n helmetTopMesh.castShadow = true\n this.object3d.add(helmetTopMesh)\n\n // Brim: thin disk in front\n const brimGeo = new THREE.CylinderGeometry(helmetR * 1.05, helmetR * 1.05, helmetR * 0.12, 16, 1, false, -Math.PI * 0.6, Math.PI * 1.2)\n const brimMesh = new THREE.Mesh(brimGeo, helmetMaterial)\n brimMesh.position.set(0, yCursor + headR * 0.85, headR * 0.1)\n this.object3d.add(brimMesh)\n\n // Chinstrap hint (thin ring around lower head)\n const strapGeo = new THREE.TorusGeometry(headR * 0.95, headR * 0.03, 6, 16)\n strapGeo.rotateX(Math.PI / 2)\n const strapMesh = new THREE.Mesh(\n strapGeo,\n new THREE.MeshStandardMaterial({ color: BELT_COLOR, roughness: 0.6 })\n )\n strapMesh.position.set(0, yCursor + headR * 0.6, 0)\n this.object3d.add(strapMesh)\n }\n\n updateDimension() {}\n\n onchange(after: Record<string, unknown>, before: Record<string, unknown>) {\n if (\n 'status' in after ||\n 'vestColor' in after ||\n 'helmetEmissive' in after ||\n 'width' in after ||\n 'height' in after ||\n 'depth' in after\n ) {\n this.update()\n return\n }\n super.onchange(after, before)\n }\n\n updateAlpha() {}\n}\n"]}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Component, ComponentNature, RealObject } from '@hatiolab/things-scene';
|
|
2
|
+
import { type Alignment, type LegendBinding, type PlacementArchetype } from '@operato/scene-base';
|
|
3
|
+
/**
|
|
4
|
+
* Worker status — what a human operator is currently doing.
|
|
5
|
+
*
|
|
6
|
+
* - `idle` — present at station but not actively working
|
|
7
|
+
* - `walking` — moving between stations
|
|
8
|
+
* - `working` — actively performing a task
|
|
9
|
+
* - `alarm` — emergency / call-for-help (e.g. line-stop pull-cord)
|
|
10
|
+
*
|
|
11
|
+
* Notably no `loaded` or `running` — workers don't carry the same kind of
|
|
12
|
+
* payload semantics as a forklift, and "running" is ambiguous for a person.
|
|
13
|
+
*/
|
|
14
|
+
export type WorkerStatus = 'idle' | 'walking' | 'working' | 'alarm';
|
|
15
|
+
declare const Base: typeof Component;
|
|
16
|
+
export default class Worker extends Base {
|
|
17
|
+
static legends: Record<string, LegendBinding>;
|
|
18
|
+
/**
|
|
19
|
+
* Worker stands on the floor — `floor` archetype with `bottom` alignment.
|
|
20
|
+
* Default depth is ~1700mm (typical adult height); the head reaches just
|
|
21
|
+
* above the operation surface, which makes a worker visually adjacent to a
|
|
22
|
+
* conveyor in a side view but clearly above it in a front view.
|
|
23
|
+
*
|
|
24
|
+
* Note this is human height, not "operation - floor" — the worker is taller
|
|
25
|
+
* than the conveyor by design. Use the explicit number form rather than the
|
|
26
|
+
* heights-derived function form.
|
|
27
|
+
*/
|
|
28
|
+
static placement: PlacementArchetype;
|
|
29
|
+
static align: Alignment;
|
|
30
|
+
static defaultDepth: number;
|
|
31
|
+
get nature(): ComponentNature;
|
|
32
|
+
get anchors(): never[];
|
|
33
|
+
/**
|
|
34
|
+
* 2D — top-down view of a person from above: shoulders / vest as the main
|
|
35
|
+
* filled silhouette (auto-filled with vestColor), helmet circle drawn over
|
|
36
|
+
* it in postrender(). The helmet sits at the *facing* direction (top of
|
|
37
|
+
* the bounds), so the orientation is readable.
|
|
38
|
+
*/
|
|
39
|
+
render(ctx: CanvasRenderingContext2D): void;
|
|
40
|
+
postrender(ctx: CanvasRenderingContext2D): void;
|
|
41
|
+
get fillStyle(): string;
|
|
42
|
+
buildRealObject(): RealObject | undefined;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
package/dist/worker.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
4
|
+
*/
|
|
5
|
+
import { RectPath, Shape, sceneComponent } from '@hatiolab/things-scene';
|
|
6
|
+
import { Legendable, Placeable } from '@operato/scene-base';
|
|
7
|
+
import { Worker3D } from './worker-3d.js';
|
|
8
|
+
/**
|
|
9
|
+
* Vest color — the hi-vis safety vest is the most visible status indicator
|
|
10
|
+
* for human workers in a smart factory floor view.
|
|
11
|
+
*/
|
|
12
|
+
const VEST_LEGEND = {
|
|
13
|
+
idle: '#FFD700', // standard hi-vis yellow
|
|
14
|
+
walking: '#FFD700',
|
|
15
|
+
working: '#FF8C00', // orange tint (busy)
|
|
16
|
+
alarm: '#FF1744', // bright red (emergency)
|
|
17
|
+
default: '#FFD700'
|
|
18
|
+
};
|
|
19
|
+
/** Helmet emissive — small accent indicator visible from above (camera view). */
|
|
20
|
+
const HELMET_EMISSIVE_LEGEND = {
|
|
21
|
+
idle: '#222222',
|
|
22
|
+
walking: '#44ff44',
|
|
23
|
+
working: '#44aaff',
|
|
24
|
+
alarm: '#ff3333',
|
|
25
|
+
default: '#222222'
|
|
26
|
+
};
|
|
27
|
+
const NATURE = {
|
|
28
|
+
mutable: false,
|
|
29
|
+
resizable: true,
|
|
30
|
+
rotatable: true,
|
|
31
|
+
properties: [
|
|
32
|
+
{
|
|
33
|
+
type: 'select',
|
|
34
|
+
label: 'status',
|
|
35
|
+
name: 'status',
|
|
36
|
+
property: {
|
|
37
|
+
options: [
|
|
38
|
+
{ display: 'Idle', value: 'idle' },
|
|
39
|
+
{ display: 'Walking', value: 'walking' },
|
|
40
|
+
{ display: 'Working', value: 'working' },
|
|
41
|
+
{ display: 'Alarm', value: 'alarm' }
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'string',
|
|
47
|
+
label: 'name',
|
|
48
|
+
name: 'workerName'
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
help: 'scene/component/worker'
|
|
52
|
+
};
|
|
53
|
+
const Base = Legendable(Placeable(RectPath(Shape)));
|
|
54
|
+
let Worker = class Worker extends Base {
|
|
55
|
+
static legends = {
|
|
56
|
+
vestColor: { from: 'status', legend: VEST_LEGEND },
|
|
57
|
+
helmetEmissive: { from: 'status', legend: HELMET_EMISSIVE_LEGEND }
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Worker stands on the floor — `floor` archetype with `bottom` alignment.
|
|
61
|
+
* Default depth is ~1700mm (typical adult height); the head reaches just
|
|
62
|
+
* above the operation surface, which makes a worker visually adjacent to a
|
|
63
|
+
* conveyor in a side view but clearly above it in a front view.
|
|
64
|
+
*
|
|
65
|
+
* Note this is human height, not "operation - floor" — the worker is taller
|
|
66
|
+
* than the conveyor by design. Use the explicit number form rather than the
|
|
67
|
+
* heights-derived function form.
|
|
68
|
+
*/
|
|
69
|
+
static placement = 'floor';
|
|
70
|
+
static align = 'bottom';
|
|
71
|
+
static defaultDepth = 1700;
|
|
72
|
+
get nature() {
|
|
73
|
+
return NATURE;
|
|
74
|
+
}
|
|
75
|
+
get anchors() {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 2D — top-down view of a person from above: shoulders / vest as the main
|
|
80
|
+
* filled silhouette (auto-filled with vestColor), helmet circle drawn over
|
|
81
|
+
* it in postrender(). The helmet sits at the *facing* direction (top of
|
|
82
|
+
* the bounds), so the orientation is readable.
|
|
83
|
+
*/
|
|
84
|
+
render(ctx) {
|
|
85
|
+
const { width, height, left, top } = this.state;
|
|
86
|
+
// Shoulders: a rounded rect, wider than tall (chest seen from above)
|
|
87
|
+
const shoulderH = height * 0.55;
|
|
88
|
+
const shoulderY = top + height * 0.30;
|
|
89
|
+
const radius = Math.min(width, shoulderH) * 0.30;
|
|
90
|
+
ctx.beginPath();
|
|
91
|
+
ctx.roundRect(left, shoulderY, width, shoulderH, radius);
|
|
92
|
+
}
|
|
93
|
+
postrender(ctx) {
|
|
94
|
+
super.postrender?.(ctx);
|
|
95
|
+
const { width, height, left, top } = this.state;
|
|
96
|
+
const cx = left + width / 2;
|
|
97
|
+
const helmetEmissive = this.state.helmetEmissive || '#222';
|
|
98
|
+
ctx.save();
|
|
99
|
+
// Reflective stripes across the vest (two horizontal bands)
|
|
100
|
+
ctx.fillStyle = '#eaeaea';
|
|
101
|
+
ctx.fillRect(left + width * 0.08, top + height * 0.45, width * 0.84, height * 0.04);
|
|
102
|
+
ctx.fillRect(left + width * 0.08, top + height * 0.65, width * 0.84, height * 0.04);
|
|
103
|
+
// Helmet circle at the top (facing direction)
|
|
104
|
+
const helmetR = Math.min(width, height * 0.4) * 0.45;
|
|
105
|
+
ctx.fillStyle = this.state.vestColor || '#FFD700';
|
|
106
|
+
ctx.strokeStyle = '#222';
|
|
107
|
+
ctx.lineWidth = 1;
|
|
108
|
+
ctx.beginPath();
|
|
109
|
+
ctx.ellipse(cx, top + helmetR + height * 0.02, helmetR, helmetR, 0, 0, Math.PI * 2);
|
|
110
|
+
ctx.fill();
|
|
111
|
+
ctx.stroke();
|
|
112
|
+
// Emissive helmet status accent (small dot on helmet)
|
|
113
|
+
ctx.fillStyle = helmetEmissive;
|
|
114
|
+
ctx.beginPath();
|
|
115
|
+
ctx.ellipse(cx, top + helmetR + height * 0.02, helmetR * 0.4, helmetR * 0.4, 0, 0, Math.PI * 2);
|
|
116
|
+
ctx.fill();
|
|
117
|
+
ctx.restore();
|
|
118
|
+
}
|
|
119
|
+
get fillStyle() {
|
|
120
|
+
return this.state.vestColor || '#FFD700';
|
|
121
|
+
}
|
|
122
|
+
buildRealObject() {
|
|
123
|
+
return new Worker3D(this);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
Worker = __decorate([
|
|
127
|
+
sceneComponent('worker')
|
|
128
|
+
], Worker);
|
|
129
|
+
export default Worker;
|
|
130
|
+
//# sourceMappingURL=worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":";AAAA;;GAEG;AACH,OAAO,EAA0C,QAAQ,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAChH,OAAO,EACL,UAAU,EACV,SAAS,EAKV,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAezC;;;GAGG;AACH,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,SAAS,EAAM,yBAAyB;IAC9C,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS,EAAG,qBAAqB;IAC1C,KAAK,EAAE,SAAS,EAAK,yBAAyB;IAC9C,OAAO,EAAE,SAAS;CACnB,CAAA;AAED,iFAAiF;AACjF,MAAM,sBAAsB,GAAG;IAC7B,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,SAAS;IAChB,OAAO,EAAE,SAAS;CACnB,CAAA;AAED,MAAM,MAAM,GAAoB;IAC9B,OAAO,EAAE,KAAK;IACd,SAAS,EAAE,IAAI;IACf,SAAS,EAAE,IAAI;IACf,UAAU,EAAE;QACV;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE;gBACR,OAAO,EAAE;oBACP,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;oBAClC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;oBACxC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE;oBACxC,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;iBACrC;aACF;SACF;QACD;YACE,IAAI,EAAE,QAAQ;YACd,KAAK,EAAE,MAAM;YACb,IAAI,EAAE,YAAY;SACnB;KACF;IACD,IAAI,EAAE,wBAAwB;CAC/B,CAAA;AAED,MAAM,IAAI,GAAG,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAgC,CAAA;AAGnE,IAAM,MAAM,GAAZ,MAAM,MAAO,SAAQ,IAAI;IACtC,MAAM,CAAC,OAAO,GAAkC;QAC9C,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE;QAClD,cAAc,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,sBAAsB,EAAE;KACnE,CAAA;IAED;;;;;;;;;OASG;IACH,MAAM,CAAC,SAAS,GAAuB,OAAO,CAAA;IAC9C,MAAM,CAAC,KAAK,GAAc,QAAQ,CAAA;IAClC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAA;IAE1B,IAAI,MAAM;QACR,OAAO,MAAM,CAAA;IACf,CAAC;IAED,IAAI,OAAO;QACT,OAAO,EAAE,CAAA;IACX,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,GAA6B;QAClC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,qEAAqE;QACrE,MAAM,SAAS,GAAG,MAAM,GAAG,IAAI,CAAA;QAC/B,MAAM,SAAS,GAAG,GAAG,GAAG,MAAM,GAAG,IAAI,CAAA;QACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,IAAI,CAAA;QAChD,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,CAAA;IAC1D,CAAC;IAED,UAAU,CAAC,GAA6B;QACtC,KAAK,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAA;QAEvB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,CAAA;QAC/C,MAAM,EAAE,GAAG,IAAI,GAAG,KAAK,GAAG,CAAC,CAAA;QAC3B,MAAM,cAAc,GAAI,IAAI,CAAC,KAAK,CAAC,cAAyB,IAAI,MAAM,CAAA;QAEtE,GAAG,CAAC,IAAI,EAAE,CAAA;QAEV,4DAA4D;QAC5D,GAAG,CAAC,SAAS,GAAG,SAAS,CAAA;QACzB,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;QACnF,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;QAEnF,8CAA8C;QAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAA;QACpD,GAAG,CAAC,SAAS,GAAI,IAAI,CAAC,KAAK,CAAC,SAAoB,IAAI,SAAS,CAAA;QAC7D,GAAG,CAAC,WAAW,GAAG,MAAM,CAAA;QACxB,GAAG,CAAC,SAAS,GAAG,CAAC,CAAA;QACjB,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QACnF,GAAG,CAAC,IAAI,EAAE,CAAA;QACV,GAAG,CAAC,MAAM,EAAE,CAAA;QAEZ,sDAAsD;QACtD,GAAG,CAAC,SAAS,GAAG,cAAc,CAAA;QAC9B,GAAG,CAAC,SAAS,EAAE,CAAA;QACf,GAAG,CAAC,OAAO,CAAC,EAAE,EAAE,GAAG,GAAG,OAAO,GAAG,MAAM,GAAG,IAAI,EAAE,OAAO,GAAG,GAAG,EAAE,OAAO,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;QAC/F,GAAG,CAAC,IAAI,EAAE,CAAA;QAEV,GAAG,CAAC,OAAO,EAAE,CAAA;IACf,CAAC;IAED,IAAI,SAAS;QACX,OAAQ,IAAI,CAAC,KAAK,CAAC,SAAoB,IAAI,SAAS,CAAA;IACtD,CAAC;IAED,eAAe;QACb,OAAO,IAAI,QAAQ,CAAC,IAAW,CAAC,CAAA;IAClC,CAAC;;AAnFkB,MAAM;IAD1B,cAAc,CAAC,QAAQ,CAAC;GACJ,MAAM,CAoF1B;eApFoB,MAAM","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n */\nimport { Component, ComponentNature, RealObject, RectPath, Shape, sceneComponent } from '@hatiolab/things-scene'\nimport {\n Legendable,\n Placeable,\n type Alignment,\n type Heights,\n type LegendBinding,\n type PlacementArchetype\n} from '@operato/scene-base'\n\nimport { Worker3D } from './worker-3d.js'\n\n/**\n * Worker status — what a human operator is currently doing.\n *\n * - `idle` — present at station but not actively working\n * - `walking` — moving between stations\n * - `working` — actively performing a task\n * - `alarm` — emergency / call-for-help (e.g. line-stop pull-cord)\n *\n * Notably no `loaded` or `running` — workers don't carry the same kind of\n * payload semantics as a forklift, and \"running\" is ambiguous for a person.\n */\nexport type WorkerStatus = 'idle' | 'walking' | 'working' | 'alarm'\n\n/**\n * Vest color — the hi-vis safety vest is the most visible status indicator\n * for human workers in a smart factory floor view.\n */\nconst VEST_LEGEND = {\n idle: '#FFD700', // standard hi-vis yellow\n walking: '#FFD700',\n working: '#FF8C00', // orange tint (busy)\n alarm: '#FF1744', // bright red (emergency)\n default: '#FFD700'\n}\n\n/** Helmet emissive — small accent indicator visible from above (camera view). */\nconst HELMET_EMISSIVE_LEGEND = {\n idle: '#222222',\n walking: '#44ff44',\n working: '#44aaff',\n alarm: '#ff3333',\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: 'Walking', value: 'walking' },\n { display: 'Working', value: 'working' },\n { display: 'Alarm', value: 'alarm' }\n ]\n }\n },\n {\n type: 'string',\n label: 'name',\n name: 'workerName'\n }\n ],\n help: 'scene/component/worker'\n}\n\nconst Base = Legendable(Placeable(RectPath(Shape))) as unknown as typeof Component\n\n@sceneComponent('worker')\nexport default class Worker extends Base {\n static legends: Record<string, LegendBinding> = {\n vestColor: { from: 'status', legend: VEST_LEGEND },\n helmetEmissive: { from: 'status', legend: HELMET_EMISSIVE_LEGEND }\n }\n\n /**\n * Worker stands on the floor — `floor` archetype with `bottom` alignment.\n * Default depth is ~1700mm (typical adult height); the head reaches just\n * above the operation surface, which makes a worker visually adjacent to a\n * conveyor in a side view but clearly above it in a front view.\n *\n * Note this is human height, not \"operation - floor\" — the worker is taller\n * than the conveyor by design. Use the explicit number form rather than the\n * heights-derived function form.\n */\n static placement: PlacementArchetype = 'floor'\n static align: Alignment = 'bottom'\n static defaultDepth = 1700\n\n get nature() {\n return NATURE\n }\n\n get anchors() {\n return []\n }\n\n /**\n * 2D — top-down view of a person from above: shoulders / vest as the main\n * filled silhouette (auto-filled with vestColor), helmet circle drawn over\n * it in postrender(). The helmet sits at the *facing* direction (top of\n * the bounds), so the orientation is readable.\n */\n render(ctx: CanvasRenderingContext2D) {\n const { width, height, left, top } = this.state\n // Shoulders: a rounded rect, wider than tall (chest seen from above)\n const shoulderH = height * 0.55\n const shoulderY = top + height * 0.30\n const radius = Math.min(width, shoulderH) * 0.30\n ctx.beginPath()\n ctx.roundRect(left, shoulderY, width, shoulderH, radius)\n }\n\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 helmetEmissive = (this.state.helmetEmissive as string) || '#222'\n\n ctx.save()\n\n // Reflective stripes across the vest (two horizontal bands)\n ctx.fillStyle = '#eaeaea'\n ctx.fillRect(left + width * 0.08, top + height * 0.45, width * 0.84, height * 0.04)\n ctx.fillRect(left + width * 0.08, top + height * 0.65, width * 0.84, height * 0.04)\n\n // Helmet circle at the top (facing direction)\n const helmetR = Math.min(width, height * 0.4) * 0.45\n ctx.fillStyle = (this.state.vestColor as string) || '#FFD700'\n ctx.strokeStyle = '#222'\n ctx.lineWidth = 1\n ctx.beginPath()\n ctx.ellipse(cx, top + helmetR + height * 0.02, helmetR, helmetR, 0, 0, Math.PI * 2)\n ctx.fill()\n ctx.stroke()\n\n // Emissive helmet status accent (small dot on helmet)\n ctx.fillStyle = helmetEmissive\n ctx.beginPath()\n ctx.ellipse(cx, top + helmetR + height * 0.02, helmetR * 0.4, helmetR * 0.4, 0, 0, Math.PI * 2)\n ctx.fill()\n\n ctx.restore()\n }\n\n get fillStyle() {\n return (this.state.vestColor as string) || '#FFD700'\n }\n\n buildRealObject(): RealObject | undefined {\n return new Worker3D(this as any)\n }\n}\n"]}
|
package/icons/agv.png
ADDED
|
Binary file
|
|
Binary file
|
package/icons/tugger.png
ADDED
|
Binary file
|
package/icons/worker.png
ADDED
|
Binary file
|