@operato/scene-urdf 10.0.0-beta.2 → 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/README.md +22 -25
- package/TODO.md +58 -0
- package/dist/editors/index.d.ts +7 -0
- package/dist/editors/index.js +12 -1
- package/dist/editors/index.js.map +1 -1
- package/dist/editors/property-editor-urdf-joints.d.ts +54 -0
- package/dist/editors/property-editor-urdf-joints.js +246 -0
- package/dist/editors/property-editor-urdf-joints.js.map +1 -0
- package/dist/editors/property-editor-urdf-preset.d.ts +8 -0
- package/dist/editors/property-editor-urdf-preset.js +114 -0
- package/dist/editors/property-editor-urdf-preset.js.map +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/joint-controller.d.ts +35 -0
- package/dist/joint-controller.js +34 -0
- package/dist/joint-controller.js.map +1 -0
- package/dist/real-object-urdf.d.ts +106 -0
- package/dist/real-object-urdf.js +527 -0
- package/dist/real-object-urdf.js.map +1 -0
- package/dist/smoothing-controller.d.ts +15 -0
- package/dist/smoothing-controller.js +88 -0
- package/dist/smoothing-controller.js.map +1 -0
- package/dist/templates/index.d.ts +3 -0
- package/dist/templates/index.js +2 -3
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/{urdf-controller.d.ts → urdf.d.ts} +3 -0
- package/dist/templates/urdf.js +19 -0
- package/dist/templates/urdf.js.map +1 -0
- package/dist/urdf-object.d.ts +264 -0
- package/dist/urdf-object.js +190 -0
- package/dist/urdf-object.js.map +1 -0
- package/dist/urdf-presets.d.ts +22 -0
- package/dist/urdf-presets.js +176 -0
- package/dist/urdf-presets.js.map +1 -0
- package/icons/urdf.png +0 -0
- package/package.json +5 -4
- package/translations/en.json +10 -16
- package/translations/ja.json +10 -16
- package/translations/ko.json +10 -16
- package/translations/ms.json +10 -16
- package/translations/zh.json +10 -16
- package/dist/elements/drag-n-drop.d.ts +0 -2
- package/dist/elements/drag-n-drop.js +0 -126
- package/dist/elements/drag-n-drop.js.map +0 -1
- package/dist/elements/urdf-controller-element.d.ts +0 -12
- package/dist/elements/urdf-controller-element.js +0 -283
- package/dist/elements/urdf-controller-element.js.map +0 -1
- package/dist/elements/urdf-drag-controls.d.ts +0 -32
- package/dist/elements/urdf-drag-controls.js +0 -197
- package/dist/elements/urdf-drag-controls.js.map +0 -1
- package/dist/elements/urdf-manipulator-element.d.ts +0 -15
- package/dist/elements/urdf-manipulator-element.js +0 -112
- package/dist/elements/urdf-manipulator-element.js.map +0 -1
- package/dist/elements/urdf-viewer-element.d.ts +0 -53
- package/dist/elements/urdf-viewer-element.js +0 -414
- package/dist/elements/urdf-viewer-element.js.map +0 -1
- package/dist/templates/urdf-controller.js +0 -16
- package/dist/templates/urdf-controller.js.map +0 -1
- package/dist/templates/urdf-viewer.d.ts +0 -14
- package/dist/templates/urdf-viewer.js +0 -16
- package/dist/templates/urdf-viewer.js.map +0 -1
- package/dist/urdf-controller.d.ts +0 -15
- package/dist/urdf-controller.js +0 -70
- package/dist/urdf-controller.js.map +0 -1
- package/dist/urdf-viewer.d.ts +0 -16
- package/dist/urdf-viewer.js +0 -202
- package/dist/urdf-viewer.js.map +0 -1
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright © HatioLab Inc. All rights reserved.
|
|
3
|
+
*
|
|
4
|
+
* URDF (Unified Robot Description Format) 지원 RealObject.
|
|
5
|
+
* RealObjectGLTF와 동일한 캐시/레이스/placeholder 패턴을 따른다.
|
|
6
|
+
*
|
|
7
|
+
* 차이점: GLTF는 노드/애니메이션이 주된 바인딩 대상이지만, URDF는 "joint"가
|
|
8
|
+
* 1급 시민이다. state.joints에 이름→라디안(또는 prismatic의 경우 거리) 맵을
|
|
9
|
+
* 두어 외부 센서값 등을 연결할 수 있도록 한다.
|
|
10
|
+
*
|
|
11
|
+
* 좌표계: URDF는 Z-up, things-scene의 3D는 Y-up. 로드 후 루트 그룹에 1회만
|
|
12
|
+
* -PI/2 X축 회전을 적용하여 변환한다. 내부 조인트 축은 원본 그대로 유지.
|
|
13
|
+
*/
|
|
14
|
+
import * as THREE from 'three';
|
|
15
|
+
import URDFLoader from 'urdf-loader';
|
|
16
|
+
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
|
|
17
|
+
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js';
|
|
18
|
+
import { warn } from '@hatiolab/things-scene';
|
|
19
|
+
import { RealObjectExternalModel } from '@hatiolab/things-scene';
|
|
20
|
+
import { RealObjectGLTF } from '@hatiolab/things-scene';
|
|
21
|
+
import { createJointController } from './joint-controller.js';
|
|
22
|
+
const X_NEG_PI_HALF = -Math.PI / 2;
|
|
23
|
+
export class RealObjectURDF extends RealObjectExternalModel {
|
|
24
|
+
get _formatLabel() { return 'urdf'; }
|
|
25
|
+
get _placeholderColor() { return 0x1976d2; }
|
|
26
|
+
// URDF 파싱 결과 캐시 — 동일 URL 재파싱 방지. clone()으로 복제 사용.
|
|
27
|
+
static _urdfCache = new Map();
|
|
28
|
+
// 탑뷰 스냅샷 캐시 (2D 렌더 폴백)
|
|
29
|
+
static _topViewCache = new Map();
|
|
30
|
+
/**
|
|
31
|
+
* URDF를 로드한다 (동일 URL 캐시).
|
|
32
|
+
* mesh 로더는 STL/DAE(내장) + OBJ(MTL 연동) + GLB(확장)를 지원한다.
|
|
33
|
+
*
|
|
34
|
+
* URDFLoader.loadAsync는 URDF XML 파싱 직후 resolve하지만 mesh(STL/DAE/GLB)는
|
|
35
|
+
* 비동기로 뒤늦게 attach된다. robot.clone() 시점에 mesh가 아직 없으면 클론된
|
|
36
|
+
* 트리엔 visual이 비게 되고 원본에만 mesh가 붙는다. 전용 LoadingManager의
|
|
37
|
+
* onLoad를 사용해 URDF + 모든 mesh가 완료된 후 resolve한다.
|
|
38
|
+
*
|
|
39
|
+
* `packages` 옵션을 통해 ROS `package://<pkg>/<rel>` prefix 해석을 설정할 수
|
|
40
|
+
* 있다 (예: `{ a1_description: 'https://.../robots/a1_description' }`).
|
|
41
|
+
*/
|
|
42
|
+
static loadURDF(url, packages) {
|
|
43
|
+
// 동일 URL이라도 packages 설정이 다르면 다른 캐시 엔트리.
|
|
44
|
+
const cacheKey = packages && Object.keys(packages).length > 0
|
|
45
|
+
? `${url}|${JSON.stringify(packages)}`
|
|
46
|
+
: url;
|
|
47
|
+
let cached = RealObjectURDF._urdfCache.get(cacheKey);
|
|
48
|
+
if (!cached) {
|
|
49
|
+
cached = new Promise((resolve, reject) => {
|
|
50
|
+
const manager = new THREE.LoadingManager();
|
|
51
|
+
const loader = new URDFLoader(manager);
|
|
52
|
+
if (packages) {
|
|
53
|
+
loader.packages = packages;
|
|
54
|
+
}
|
|
55
|
+
loader.loadMeshCb = ((path, mgr, done) => {
|
|
56
|
+
if (/\.(glb|gltf)$/i.test(path)) {
|
|
57
|
+
// GLB는 별도 시스템 — manager 추적에서 벗어나므로 명시적으로 track.
|
|
58
|
+
mgr.itemStart(path);
|
|
59
|
+
RealObjectGLTF.loadGLTF(path).then(gltf => { done(gltf.scene.clone()); mgr.itemEnd(path); }, err => { done(null, err); mgr.itemError(path); mgr.itemEnd(path); });
|
|
60
|
+
}
|
|
61
|
+
else if (/\.obj$/i.test(path)) {
|
|
62
|
+
// OBJ는 urdf-loader 내장 defaultMeshLoader가 지원하지 않음.
|
|
63
|
+
// 연관된 .mtl(재질) 파일이 있으면 MTLLoader로 먼저 로드하여 색상/텍스처
|
|
64
|
+
// 적용, 없으면 OBJLoader만 폴백.
|
|
65
|
+
//
|
|
66
|
+
// LoadingManager 정합성: 외부 path로만 itemStart/itemEnd를 1회씩
|
|
67
|
+
// 기록한다. 내부 MTLLoader/OBJLoader는 manager 주입 없이 생성하여
|
|
68
|
+
// 서브-아이템으로 카운트되지 않게 함 — 그렇지 않으면 MTL 404 직후
|
|
69
|
+
// 일시적으로 pending=0이 되어 manager.onLoad가 조기 발화하고
|
|
70
|
+
// 최종 로봇이 빈 상태로 resolve되는 레이스가 발생.
|
|
71
|
+
mgr.itemStart(path);
|
|
72
|
+
const finishOk = (mesh) => { done(mesh); mgr.itemEnd(path); };
|
|
73
|
+
const finishErr = (err) => { done(null, err); mgr.itemError(path); mgr.itemEnd(path); };
|
|
74
|
+
const lastSlash = path.lastIndexOf('/');
|
|
75
|
+
const baseUrl = lastSlash >= 0 ? path.substring(0, lastSlash + 1) : '';
|
|
76
|
+
const mtlName = path.substring(lastSlash + 1).replace(/\.obj$/i, '.mtl');
|
|
77
|
+
const loadObjOnly = () => {
|
|
78
|
+
new OBJLoader().load(path, group => finishOk(group), undefined, err => finishErr(err));
|
|
79
|
+
};
|
|
80
|
+
new MTLLoader().setResourcePath(baseUrl).setPath(baseUrl).load(mtlName, materials => {
|
|
81
|
+
materials.preload();
|
|
82
|
+
new OBJLoader().setMaterials(materials).load(path, group => finishOk(group), undefined, err => finishErr(err));
|
|
83
|
+
}, undefined, () => {
|
|
84
|
+
// MTL 로드 실패 시 OBJ만 로드 (정상 시나리오 — MTL 없는 OBJ도 있음)
|
|
85
|
+
loadObjOnly();
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// STL/DAE는 urdf-loader 내장 디폴트로 폴백 (manager 자동 추적)
|
|
90
|
+
;
|
|
91
|
+
loader.defaultMeshLoader(path, mgr, done);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
let robotResult;
|
|
95
|
+
manager.onLoad = () => {
|
|
96
|
+
if (robotResult)
|
|
97
|
+
resolve(robotResult);
|
|
98
|
+
else
|
|
99
|
+
reject(new Error('URDF load completed but no robot result'));
|
|
100
|
+
};
|
|
101
|
+
manager.onError = (failedUrl) => {
|
|
102
|
+
// 개별 에러는 로그로만 남김 — 전체 로드는 그래도 완료되도록
|
|
103
|
+
// (일부 mesh 실패 시 나머지라도 보이게)
|
|
104
|
+
warn('URDFLoader: resource load error', failedUrl);
|
|
105
|
+
};
|
|
106
|
+
loader.load(url, robot => { robotResult = robot; }, undefined, err => reject(err));
|
|
107
|
+
}).catch(err => {
|
|
108
|
+
RealObjectURDF._urdfCache.delete(cacheKey);
|
|
109
|
+
throw err;
|
|
110
|
+
});
|
|
111
|
+
RealObjectURDF._urdfCache.set(cacheKey, cached);
|
|
112
|
+
}
|
|
113
|
+
return cached;
|
|
114
|
+
}
|
|
115
|
+
/** 모든 캐시 비움 (보드 전환 시 호출) */
|
|
116
|
+
static flushCache() {
|
|
117
|
+
RealObjectURDF._urdfCache.clear();
|
|
118
|
+
RealObjectURDF._topViewCache.clear();
|
|
119
|
+
}
|
|
120
|
+
static getTopViewCache(source) {
|
|
121
|
+
return RealObjectURDF._topViewCache.get(source);
|
|
122
|
+
}
|
|
123
|
+
static setTopViewCache(source, canvas) {
|
|
124
|
+
RealObjectURDF._topViewCache.set(source, canvas);
|
|
125
|
+
}
|
|
126
|
+
// --- 인스턴스 상태 ---
|
|
127
|
+
_robot;
|
|
128
|
+
// 조인트 인덱스 + 메타 (로드 후 외부에서 구독 가능)
|
|
129
|
+
_jointIndex = new Map();
|
|
130
|
+
_jointMeta = new Map();
|
|
131
|
+
// 원본 조인트 값 — reset을 위해 보존
|
|
132
|
+
_jointOriginals = new Map();
|
|
133
|
+
// 자동 애니메이션 (sine 모드) 상태
|
|
134
|
+
_animRaf;
|
|
135
|
+
_animStartTime = 0;
|
|
136
|
+
// Joint controller (physics mode) 상태
|
|
137
|
+
_controller;
|
|
138
|
+
_ctrlRaf;
|
|
139
|
+
_ctrlLastTime = 0;
|
|
140
|
+
get robot() {
|
|
141
|
+
return this._robot;
|
|
142
|
+
}
|
|
143
|
+
/** 로드된 조인트 메타 리스트 (property panel 등이 참조) */
|
|
144
|
+
get jointMeta() {
|
|
145
|
+
return Array.from(this._jointMeta.values());
|
|
146
|
+
}
|
|
147
|
+
get jointNames() {
|
|
148
|
+
return Array.from(this._jointIndex.keys());
|
|
149
|
+
}
|
|
150
|
+
_loadExternal(url) {
|
|
151
|
+
const packages = this.component.state.packages;
|
|
152
|
+
return RealObjectURDF.loadURDF(url, packages);
|
|
153
|
+
}
|
|
154
|
+
_onLoaded(robot) {
|
|
155
|
+
if (this.component.state.loadError) {
|
|
156
|
+
this.component.setState({ loadError: undefined });
|
|
157
|
+
}
|
|
158
|
+
// 캐시의 robot은 공유 참조이므로 clone하여 독립 인스턴스 구성.
|
|
159
|
+
// urdf-loader의 URDFRobot는 Object3D를 상속하므로 Object3D.clone()이 안전하게 동작.
|
|
160
|
+
const cloned = robot.clone();
|
|
161
|
+
// 섀도우 캐스팅
|
|
162
|
+
cloned.traverse(child => {
|
|
163
|
+
if (child.isMesh) {
|
|
164
|
+
child.castShadow = true;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
// clear()는 object3d.userData를 리셋하므로 pending 후 재설정.
|
|
168
|
+
this.clear();
|
|
169
|
+
this.object3d.userData.context = this;
|
|
170
|
+
this._robot = cloned;
|
|
171
|
+
// 좌표계 변환: URDF(Z-up) → three(Y-up).
|
|
172
|
+
// 사용자가 state.upAxis = 'y' 로 지정하면 변환 생략.
|
|
173
|
+
this.pivot = new THREE.Object3D();
|
|
174
|
+
const upAxis = this.component.state.upAxis;
|
|
175
|
+
if (upAxis !== 'y') {
|
|
176
|
+
this.pivot.rotation.x = X_NEG_PI_HALF;
|
|
177
|
+
}
|
|
178
|
+
// URDF는 기본 meter 단위. state.unitScale로 에디터 단위로 환산.
|
|
179
|
+
const unitScale = this._resolveUnitScale();
|
|
180
|
+
if (unitScale !== 1) {
|
|
181
|
+
this.pivot.scale.setScalar(unitScale);
|
|
182
|
+
}
|
|
183
|
+
this.pivot.add(cloned);
|
|
184
|
+
// URDF의 base_link는 관행상 z=0이 바닥(floor origin). things-scene는 center
|
|
185
|
+
// origin이므로, components3D 그룹(= -geometricOffsetY만큼 shift된 바닥 기준
|
|
186
|
+
// subgroup)에 pivot을 넣어 로봇이 컴포넌트 바닥면에 서도록 한다.
|
|
187
|
+
this.components3D.add(this.pivot);
|
|
188
|
+
// 조인트 인덱스/메타 구축
|
|
189
|
+
this._buildJointIndex(cloned);
|
|
190
|
+
// bounding box 계산 후 updateDimension으로 크기 반영
|
|
191
|
+
const box = new THREE.Box3().setFromObject(this.pivot);
|
|
192
|
+
this._objectSize = box.getSize(new THREE.Vector3());
|
|
193
|
+
this.updateDimension();
|
|
194
|
+
// 자동 placement 정렬: URDF의 base_link 원점이 로봇 기하의 최저점이 아닐 때
|
|
195
|
+
// (Nao/minitaur/quadrotor는 torso/중심 기준) 로봇 일부가 컴포넌트 범위를
|
|
196
|
+
// 벗어나는 것을 방지. 씬의 placement 모드에 따라 정렬 기준이 다르다.
|
|
197
|
+
// - floor: 로봇 최저점 → 컴포넌트 바닥면
|
|
198
|
+
// - inverted: 로봇 최상점 → 컴포넌트 천장면 (매달림)
|
|
199
|
+
// - space: 정렬 없음 (중심 원점 유지)
|
|
200
|
+
// state.floorAlign === false 로 비활성화 가능.
|
|
201
|
+
if (this.component.state.floorAlign !== false) {
|
|
202
|
+
const placement = this.scenePlacement;
|
|
203
|
+
if (placement !== 'space') {
|
|
204
|
+
this.components3D.updateMatrixWorld(true);
|
|
205
|
+
const worldBox = new THREE.Box3().setFromObject(this.pivot);
|
|
206
|
+
if (isFinite(worldBox.min.y) && isFinite(worldBox.max.y)) {
|
|
207
|
+
if (placement === 'inverted') {
|
|
208
|
+
const localMax = this.components3D.worldToLocal(worldBox.max.clone());
|
|
209
|
+
if (localMax.y > 0) {
|
|
210
|
+
this.pivot.position.y -= localMax.y;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// floor (기본)
|
|
215
|
+
const localMin = this.components3D.worldToLocal(worldBox.min.clone());
|
|
216
|
+
if (localMin.y < 0) {
|
|
217
|
+
this.pivot.position.y -= localMin.y;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// 컨트롤러 초기화 (state.physics 설정된 경우)
|
|
224
|
+
const physicsMode = this.component.state.physics;
|
|
225
|
+
if (physicsMode && physicsMode !== 'none') {
|
|
226
|
+
this._setupController(physicsMode);
|
|
227
|
+
}
|
|
228
|
+
// 초기 joint 상태 적용
|
|
229
|
+
const jointStates = this.component.state.joints;
|
|
230
|
+
if (jointStates) {
|
|
231
|
+
this._dispatchJointStates(jointStates);
|
|
232
|
+
}
|
|
233
|
+
// 자동 애니메이션 시작 (state.autoAnimate === 'sine'인 경우)
|
|
234
|
+
if (this.component.state.autoAnimate === 'sine') {
|
|
235
|
+
this._startAutoAnimate();
|
|
236
|
+
}
|
|
237
|
+
// 탑뷰 스냅샷
|
|
238
|
+
const source = this.component.state.src;
|
|
239
|
+
if (source && !RealObjectURDF._topViewCache.has(source)) {
|
|
240
|
+
try {
|
|
241
|
+
const canvas = RealObjectGLTF.renderTopView(this.pivot);
|
|
242
|
+
RealObjectURDF._topViewCache.set(source, canvas);
|
|
243
|
+
this.component._topViewSnapshot = canvas;
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// 스냅샷 실패는 치명적이지 않음
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else if (source) {
|
|
250
|
+
;
|
|
251
|
+
this.component._topViewSnapshot = RealObjectURDF._topViewCache.get(source);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
_resolveUnitScale() {
|
|
255
|
+
const s = this.component.state.unitScale;
|
|
256
|
+
if (typeof s === 'number' && s > 0)
|
|
257
|
+
return s;
|
|
258
|
+
// 기본: URDF meter → 에디터 mm 스케일 1000
|
|
259
|
+
return 1000;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* 조인트 트리를 순회하며 인덱스와 메타를 구축한다.
|
|
263
|
+
* fixed 조인트는 조작 대상이 아니므로 메타에서 제외하되 인덱스에는 남긴다.
|
|
264
|
+
*/
|
|
265
|
+
_buildJointIndex(robot) {
|
|
266
|
+
this._jointIndex.clear();
|
|
267
|
+
this._jointMeta.clear();
|
|
268
|
+
this._jointOriginals.clear();
|
|
269
|
+
const joints = robot.joints || {};
|
|
270
|
+
for (const [name, joint] of Object.entries(joints)) {
|
|
271
|
+
this._jointIndex.set(name, joint);
|
|
272
|
+
this._jointOriginals.set(name, joint.angle || 0);
|
|
273
|
+
if (joint.jointType === 'fixed')
|
|
274
|
+
continue;
|
|
275
|
+
this._jointMeta.set(name, {
|
|
276
|
+
name,
|
|
277
|
+
type: joint.jointType,
|
|
278
|
+
axis: { x: joint.axis.x, y: joint.axis.y, z: joint.axis.z },
|
|
279
|
+
limit: { ...joint.limit }
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* state.joints 변경을 적절한 경로로 디스패치한다.
|
|
285
|
+
* - controller가 활성이면 setTargets (controller가 스무딩/물리 적용)
|
|
286
|
+
* - 없으면 즉시 적용 (direct 모드, 기본)
|
|
287
|
+
*/
|
|
288
|
+
_dispatchJointStates(states) {
|
|
289
|
+
if (this._controller) {
|
|
290
|
+
const targets = {};
|
|
291
|
+
for (const [name, raw] of Object.entries(states)) {
|
|
292
|
+
const v = typeof raw === 'number' ? raw : Number(raw?.value);
|
|
293
|
+
if (Number.isFinite(v))
|
|
294
|
+
targets[name] = v;
|
|
295
|
+
}
|
|
296
|
+
this._controller.setTargets(targets);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
this._applyJointStates(states);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Controller를 인스턴스화하고 setup + tick 루프 시작.
|
|
303
|
+
* 이미 활성 중이면 먼저 해제.
|
|
304
|
+
*/
|
|
305
|
+
_setupController(mode) {
|
|
306
|
+
this._disposeController();
|
|
307
|
+
const ctrl = createJointController(mode);
|
|
308
|
+
if (!ctrl) {
|
|
309
|
+
warn(`URDFObject: unknown physics mode '${mode}'`);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (!this._robot)
|
|
313
|
+
return;
|
|
314
|
+
ctrl.setup({ robot: this._robot, joints: this._jointIndex, jointMeta: this._jointMeta });
|
|
315
|
+
this._controller = ctrl;
|
|
316
|
+
// 현재 state.joints를 초기 타겟으로 설정
|
|
317
|
+
const s = this.component.state.joints;
|
|
318
|
+
if (s) {
|
|
319
|
+
const targets = {};
|
|
320
|
+
for (const [name, raw] of Object.entries(s)) {
|
|
321
|
+
const v = typeof raw === 'number' ? raw : Number(raw?.value);
|
|
322
|
+
if (Number.isFinite(v))
|
|
323
|
+
targets[name] = v;
|
|
324
|
+
}
|
|
325
|
+
ctrl.setTargets(targets);
|
|
326
|
+
}
|
|
327
|
+
this._ctrlLastTime = performance.now();
|
|
328
|
+
const tick = () => {
|
|
329
|
+
if (!this._controller) {
|
|
330
|
+
this._ctrlRaf = undefined;
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const now = performance.now();
|
|
334
|
+
const dt = (now - this._ctrlLastTime) / 1000;
|
|
335
|
+
this._ctrlLastTime = now;
|
|
336
|
+
const changed = this._controller.tick(dt);
|
|
337
|
+
if (changed) {
|
|
338
|
+
this.component?.invalidate?.();
|
|
339
|
+
}
|
|
340
|
+
this._ctrlRaf = requestAnimationFrame(tick);
|
|
341
|
+
};
|
|
342
|
+
this._ctrlRaf = requestAnimationFrame(tick);
|
|
343
|
+
}
|
|
344
|
+
_disposeController() {
|
|
345
|
+
if (this._ctrlRaf != null) {
|
|
346
|
+
cancelAnimationFrame(this._ctrlRaf);
|
|
347
|
+
this._ctrlRaf = undefined;
|
|
348
|
+
}
|
|
349
|
+
if (this._controller) {
|
|
350
|
+
this._controller.dispose();
|
|
351
|
+
this._controller = undefined;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* state.joints 맵을 순회하며 각 조인트 값을 적용한다 (direct 모드).
|
|
356
|
+
* 값은 숫자 또는 { value: number } 형식 모두 허용 (유연성).
|
|
357
|
+
*
|
|
358
|
+
* joint transform 변경 후 component.invalidate()로 재렌더를 요청한다.
|
|
359
|
+
* 표준 경로: invalidate → Layer.throttle_render → rAF __draw__ → trigger('redraw')
|
|
360
|
+
* → ThreeCapability.animate → renderThreeD.
|
|
361
|
+
*/
|
|
362
|
+
_applyJointStates(states) {
|
|
363
|
+
if (!this._robot)
|
|
364
|
+
return;
|
|
365
|
+
let changed = false;
|
|
366
|
+
for (const [name, raw] of Object.entries(states)) {
|
|
367
|
+
const joint = this._jointIndex.get(name);
|
|
368
|
+
if (!joint || joint.jointType === 'fixed')
|
|
369
|
+
continue;
|
|
370
|
+
const value = typeof raw === 'number' ? raw : raw?.value;
|
|
371
|
+
if (typeof value !== 'number' || !isFinite(value))
|
|
372
|
+
continue;
|
|
373
|
+
joint.setJointValue(value);
|
|
374
|
+
changed = true;
|
|
375
|
+
}
|
|
376
|
+
if (changed) {
|
|
377
|
+
this.component?.invalidate?.();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* 자동 sine 애니메이션 시작.
|
|
382
|
+
* 각 non-fixed 조인트를 limit 범위 내에서 sine 스윙. 조인트 순서에 따라
|
|
383
|
+
* frequency와 phase를 약간씩 다르게 해 동기화를 피하고 유기적 움직임을 만든다.
|
|
384
|
+
*
|
|
385
|
+
* state.joints를 건드리지 않고 joint.setJointValue만 직접 호출 — state 변경
|
|
386
|
+
* cascade를 피해 성능을 유지하고, 슬라이더가 있는 경우에도 state와 시각이
|
|
387
|
+
* 분리되어 표시됨 (활성 시 state는 의미 없음).
|
|
388
|
+
*/
|
|
389
|
+
_startAutoAnimate() {
|
|
390
|
+
if (this._animRaf != null)
|
|
391
|
+
return;
|
|
392
|
+
if (!this._robot || this._jointMeta.size === 0)
|
|
393
|
+
return;
|
|
394
|
+
this._animStartTime = performance.now();
|
|
395
|
+
const TWO_PI = Math.PI * 2;
|
|
396
|
+
const tick = () => {
|
|
397
|
+
if (!this._robot) {
|
|
398
|
+
this._animRaf = undefined;
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const t = (performance.now() - this._animStartTime) / 1000;
|
|
402
|
+
let i = 0;
|
|
403
|
+
let changed = false;
|
|
404
|
+
for (const [name, meta] of this._jointMeta) {
|
|
405
|
+
const joint = this._jointIndex.get(name);
|
|
406
|
+
if (!joint)
|
|
407
|
+
continue;
|
|
408
|
+
const mid = (meta.limit.upper + meta.limit.lower) / 2;
|
|
409
|
+
const amp = (meta.limit.upper - meta.limit.lower) / 2;
|
|
410
|
+
// 조인트별로 frequency와 phase 약간씩 다르게 (0.15 ~ 0.4 Hz 범위)
|
|
411
|
+
const freq = 0.15 + (i % 6) * 0.05;
|
|
412
|
+
const phase = i * 0.73;
|
|
413
|
+
const value = mid + amp * Math.sin(t * freq * TWO_PI + phase);
|
|
414
|
+
joint.setJointValue(value);
|
|
415
|
+
changed = true;
|
|
416
|
+
i++;
|
|
417
|
+
}
|
|
418
|
+
if (changed) {
|
|
419
|
+
this.component?.invalidate?.();
|
|
420
|
+
}
|
|
421
|
+
this._animRaf = requestAnimationFrame(tick);
|
|
422
|
+
};
|
|
423
|
+
this._animRaf = requestAnimationFrame(tick);
|
|
424
|
+
}
|
|
425
|
+
_stopAutoAnimate() {
|
|
426
|
+
if (this._animRaf != null) {
|
|
427
|
+
cancelAnimationFrame(this._animRaf);
|
|
428
|
+
this._animRaf = undefined;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* 모든 조인트를 원본 값으로 복원한다.
|
|
433
|
+
*/
|
|
434
|
+
_resetAllJoints() {
|
|
435
|
+
if (!this._robot)
|
|
436
|
+
return;
|
|
437
|
+
for (const [name, orig] of this._jointOriginals) {
|
|
438
|
+
const joint = this._jointIndex.get(name);
|
|
439
|
+
if (joint && joint.jointType !== 'fixed') {
|
|
440
|
+
joint.setJointValue(orig);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
// --- 라이프사이클 ---
|
|
445
|
+
clear() {
|
|
446
|
+
this._stopAutoAnimate();
|
|
447
|
+
this._disposeController();
|
|
448
|
+
this._jointIndex.clear();
|
|
449
|
+
this._jointMeta.clear();
|
|
450
|
+
this._jointOriginals.clear();
|
|
451
|
+
this._robot = undefined;
|
|
452
|
+
return super.clear();
|
|
453
|
+
}
|
|
454
|
+
updateDimension() {
|
|
455
|
+
if (!this.pivot)
|
|
456
|
+
return;
|
|
457
|
+
const { width = 1, height = 1, depth = 1 } = this.component.state;
|
|
458
|
+
const { x = 1, y = 1, z = 1 } = this._objectSize || {};
|
|
459
|
+
// URDF는 관절 기반 로봇의 비율을 유지해야 하므로 uniform scale을 적용한다.
|
|
460
|
+
// 세 축 각각의 비율 중 가장 작은 값(best-fit)을 사용해 로봇이 컴포넌트의
|
|
461
|
+
// bounding box 안에 왜곡 없이 맞춰지도록 한다.
|
|
462
|
+
const unit = this._resolveUnitScale();
|
|
463
|
+
const baseX = x || 1;
|
|
464
|
+
const baseY = y || 1;
|
|
465
|
+
const baseZ = z || 1;
|
|
466
|
+
const upAxis = this.component.state.upAxis;
|
|
467
|
+
const rotated = upAxis !== 'y';
|
|
468
|
+
const rX = width / baseX;
|
|
469
|
+
const rY = (rotated ? depth : height) / baseY;
|
|
470
|
+
const rZ = (rotated ? height : depth) / baseZ;
|
|
471
|
+
const ratio = Math.min(rX, rY, rZ);
|
|
472
|
+
this.pivot.scale.setScalar(ratio * unit);
|
|
473
|
+
this.component.invalidate();
|
|
474
|
+
}
|
|
475
|
+
onchange(after, before) {
|
|
476
|
+
super.onchange(after, before);
|
|
477
|
+
if ('src' in after) {
|
|
478
|
+
this.updateSource();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if ('joints' in after) {
|
|
482
|
+
// 사용자가 joint를 직접 조작하면 자동 애니메이션이 이를 즉시 덮어써
|
|
483
|
+
// 의도를 상쇄하므로, 자동 애니메이션을 우선 중지한다.
|
|
484
|
+
if (this._animRaf != null) {
|
|
485
|
+
this._stopAutoAnimate();
|
|
486
|
+
this._component.setState({ autoAnimate: 'none' });
|
|
487
|
+
}
|
|
488
|
+
const jointStates = after.joints;
|
|
489
|
+
if (this._controller) {
|
|
490
|
+
// controller 모드: 타겟만 갱신. 이전 값 리셋은 controller가 연속 감쇠로
|
|
491
|
+
// 알아서 수렴하므로 불필요.
|
|
492
|
+
if (jointStates && Object.keys(jointStates).length > 0) {
|
|
493
|
+
this._dispatchJointStates(jointStates);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
// direct 모드: 전체 reset 후 명시된 값만 재적용 (sparse state 지원).
|
|
498
|
+
this._resetAllJoints();
|
|
499
|
+
if (jointStates && Object.keys(jointStates).length > 0) {
|
|
500
|
+
this._applyJointStates(jointStates);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if ('autoAnimate' in after) {
|
|
505
|
+
if (after.autoAnimate === 'sine') {
|
|
506
|
+
this._startAutoAnimate();
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
this._stopAutoAnimate();
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if ('physics' in after) {
|
|
513
|
+
const mode = after.physics;
|
|
514
|
+
if (mode && mode !== 'none') {
|
|
515
|
+
this._setupController(mode);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
this._disposeController();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
if ('upAxis' in after || 'unitScale' in after || 'packages' in after) {
|
|
522
|
+
// 좌표/스케일/패키지 매핑 변경은 pivot 재구성이 필요하므로 전체 재빌드
|
|
523
|
+
this.build();
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
//# sourceMappingURL=real-object-urdf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"real-object-urdf.js","sourceRoot":"","sources":["../src/real-object-urdf.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * Copyright © HatioLab Inc. All rights reserved.\n *\n * URDF (Unified Robot Description Format) 지원 RealObject.\n * RealObjectGLTF와 동일한 캐시/레이스/placeholder 패턴을 따른다.\n *\n * 차이점: GLTF는 노드/애니메이션이 주된 바인딩 대상이지만, URDF는 \"joint\"가\n * 1급 시민이다. state.joints에 이름→라디안(또는 prismatic의 경우 거리) 맵을\n * 두어 외부 센서값 등을 연결할 수 있도록 한다.\n *\n * 좌표계: URDF는 Z-up, things-scene의 3D는 Y-up. 로드 후 루트 그룹에 1회만\n * -PI/2 X축 회전을 적용하여 변환한다. 내부 조인트 축은 원본 그대로 유지.\n */\n\nimport * as THREE from 'three'\nimport URDFLoader from 'urdf-loader'\nimport type { URDFRobot, URDFJoint } from 'urdf-loader'\nimport { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'\nimport { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'\n\nimport { warn } from '@hatiolab/things-scene'\nimport type { Properties } from '@hatiolab/things-scene'\n\nimport { RealObjectExternalModel } from '@hatiolab/things-scene'\nimport { RealObjectGLTF } from '@hatiolab/things-scene'\n\nimport { createJointController, type JointController } from './joint-controller.js'\n\nexport interface URDFJointState {\n /** revolute/continuous: 라디안. prismatic: 거리(URDF 단위). */\n value?: number\n}\n\nexport interface URDFJointMeta {\n name: string\n type: 'fixed' | 'continuous' | 'revolute' | 'planar' | 'prismatic' | 'floating'\n axis: { x: number; y: number; z: number }\n limit: { lower: number; upper: number; effort: number; velocity: number }\n}\n\nconst X_NEG_PI_HALF = -Math.PI / 2\n\nexport class RealObjectURDF extends RealObjectExternalModel<URDFRobot> {\n protected get _formatLabel() { return 'urdf' }\n protected get _placeholderColor() { return 0x1976d2 }\n // URDF 파싱 결과 캐시 — 동일 URL 재파싱 방지. clone()으로 복제 사용.\n private static _urdfCache = new Map<string, Promise<URDFRobot>>()\n\n // 탑뷰 스냅샷 캐시 (2D 렌더 폴백)\n private static _topViewCache = new Map<string, HTMLCanvasElement>()\n\n /**\n * URDF를 로드한다 (동일 URL 캐시).\n * mesh 로더는 STL/DAE(내장) + OBJ(MTL 연동) + GLB(확장)를 지원한다.\n *\n * URDFLoader.loadAsync는 URDF XML 파싱 직후 resolve하지만 mesh(STL/DAE/GLB)는\n * 비동기로 뒤늦게 attach된다. robot.clone() 시점에 mesh가 아직 없으면 클론된\n * 트리엔 visual이 비게 되고 원본에만 mesh가 붙는다. 전용 LoadingManager의\n * onLoad를 사용해 URDF + 모든 mesh가 완료된 후 resolve한다.\n *\n * `packages` 옵션을 통해 ROS `package://<pkg>/<rel>` prefix 해석을 설정할 수\n * 있다 (예: `{ a1_description: 'https://.../robots/a1_description' }`).\n */\n static loadURDF(url: string, packages?: Record<string, string>): Promise<URDFRobot> {\n // 동일 URL이라도 packages 설정이 다르면 다른 캐시 엔트리.\n const cacheKey = packages && Object.keys(packages).length > 0\n ? `${url}|${JSON.stringify(packages)}`\n : url\n let cached = RealObjectURDF._urdfCache.get(cacheKey)\n if (!cached) {\n cached = new Promise<URDFRobot>((resolve, reject) => {\n const manager = new THREE.LoadingManager()\n const loader = new URDFLoader(manager)\n if (packages) {\n loader.packages = packages\n }\n\n loader.loadMeshCb = ((path: string, mgr: THREE.LoadingManager, done: (mesh: any, err?: Error) => void) => {\n if (/\\.(glb|gltf)$/i.test(path)) {\n // GLB는 별도 시스템 — manager 추적에서 벗어나므로 명시적으로 track.\n mgr.itemStart(path)\n RealObjectGLTF.loadGLTF(path).then(\n gltf => { done(gltf.scene.clone()); mgr.itemEnd(path) },\n err => { done(null, err); mgr.itemError(path); mgr.itemEnd(path) }\n )\n } else if (/\\.obj$/i.test(path)) {\n // OBJ는 urdf-loader 내장 defaultMeshLoader가 지원하지 않음.\n // 연관된 .mtl(재질) 파일이 있으면 MTLLoader로 먼저 로드하여 색상/텍스처\n // 적용, 없으면 OBJLoader만 폴백.\n //\n // LoadingManager 정합성: 외부 path로만 itemStart/itemEnd를 1회씩\n // 기록한다. 내부 MTLLoader/OBJLoader는 manager 주입 없이 생성하여\n // 서브-아이템으로 카운트되지 않게 함 — 그렇지 않으면 MTL 404 직후\n // 일시적으로 pending=0이 되어 manager.onLoad가 조기 발화하고\n // 최종 로봇이 빈 상태로 resolve되는 레이스가 발생.\n mgr.itemStart(path)\n const finishOk = (mesh: any) => { done(mesh); mgr.itemEnd(path) }\n const finishErr = (err: Error) => { done(null, err); mgr.itemError(path); mgr.itemEnd(path) }\n\n const lastSlash = path.lastIndexOf('/')\n const baseUrl = lastSlash >= 0 ? path.substring(0, lastSlash + 1) : ''\n const mtlName = path.substring(lastSlash + 1).replace(/\\.obj$/i, '.mtl')\n\n const loadObjOnly = () => {\n new OBJLoader().load(\n path,\n group => finishOk(group),\n undefined,\n err => finishErr(err as Error)\n )\n }\n new MTLLoader().setResourcePath(baseUrl).setPath(baseUrl).load(\n mtlName,\n materials => {\n materials.preload()\n new OBJLoader().setMaterials(materials).load(\n path,\n group => finishOk(group),\n undefined,\n err => finishErr(err as Error)\n )\n },\n undefined,\n () => {\n // MTL 로드 실패 시 OBJ만 로드 (정상 시나리오 — MTL 없는 OBJ도 있음)\n loadObjOnly()\n }\n )\n } else {\n // STL/DAE는 urdf-loader 내장 디폴트로 폴백 (manager 자동 추적)\n ;(loader as any).defaultMeshLoader(path, mgr, done)\n }\n }) as any\n\n let robotResult: URDFRobot | undefined\n\n manager.onLoad = () => {\n if (robotResult) resolve(robotResult)\n else reject(new Error('URDF load completed but no robot result'))\n }\n manager.onError = (failedUrl: string) => {\n // 개별 에러는 로그로만 남김 — 전체 로드는 그래도 완료되도록\n // (일부 mesh 실패 시 나머지라도 보이게)\n warn('URDFLoader: resource load error', failedUrl)\n }\n\n loader.load(\n url,\n robot => { robotResult = robot },\n undefined,\n err => reject(err)\n )\n }).catch(err => {\n RealObjectURDF._urdfCache.delete(cacheKey)\n throw err\n })\n RealObjectURDF._urdfCache.set(cacheKey, cached)\n }\n return cached\n }\n\n /** 모든 캐시 비움 (보드 전환 시 호출) */\n static flushCache() {\n RealObjectURDF._urdfCache.clear()\n RealObjectURDF._topViewCache.clear()\n }\n\n static getTopViewCache(source: string): HTMLCanvasElement | undefined {\n return RealObjectURDF._topViewCache.get(source)\n }\n\n static setTopViewCache(source: string, canvas: HTMLCanvasElement) {\n RealObjectURDF._topViewCache.set(source, canvas)\n }\n\n // --- 인스턴스 상태 ---\n\n private _robot?: URDFRobot\n\n // 조인트 인덱스 + 메타 (로드 후 외부에서 구독 가능)\n private _jointIndex = new Map<string, URDFJoint>()\n private _jointMeta = new Map<string, URDFJointMeta>()\n\n // 원본 조인트 값 — reset을 위해 보존\n private _jointOriginals = new Map<string, number>()\n\n // 자동 애니메이션 (sine 모드) 상태\n private _animRaf?: number\n private _animStartTime = 0\n\n // Joint controller (physics mode) 상태\n private _controller?: JointController\n private _ctrlRaf?: number\n private _ctrlLastTime = 0\n\n get robot(): URDFRobot | undefined {\n return this._robot\n }\n\n /** 로드된 조인트 메타 리스트 (property panel 등이 참조) */\n get jointMeta(): URDFJointMeta[] {\n return Array.from(this._jointMeta.values())\n }\n\n get jointNames(): string[] {\n return Array.from(this._jointIndex.keys())\n }\n\n protected _loadExternal(url: string): Promise<URDFRobot> {\n const packages = this.component.state.packages as Record<string, string> | undefined\n return RealObjectURDF.loadURDF(url, packages)\n }\n\n protected _onLoaded(robot: URDFRobot) {\n if (this.component.state.loadError) {\n this.component.setState({ loadError: undefined })\n }\n\n // 캐시의 robot은 공유 참조이므로 clone하여 독립 인스턴스 구성.\n // urdf-loader의 URDFRobot는 Object3D를 상속하므로 Object3D.clone()이 안전하게 동작.\n const cloned = robot.clone() as URDFRobot\n\n // 섀도우 캐스팅\n cloned.traverse(child => {\n if ((child as THREE.Mesh).isMesh) {\n child.castShadow = true\n }\n })\n\n // clear()는 object3d.userData를 리셋하므로 pending 후 재설정.\n this.clear()\n this.object3d.userData.context = this\n\n this._robot = cloned\n\n // 좌표계 변환: URDF(Z-up) → three(Y-up).\n // 사용자가 state.upAxis = 'y' 로 지정하면 변환 생략.\n this.pivot = new THREE.Object3D()\n const upAxis = this.component.state.upAxis\n if (upAxis !== 'y') {\n this.pivot.rotation.x = X_NEG_PI_HALF\n }\n\n // URDF는 기본 meter 단위. state.unitScale로 에디터 단위로 환산.\n const unitScale = this._resolveUnitScale()\n if (unitScale !== 1) {\n this.pivot.scale.setScalar(unitScale)\n }\n\n this.pivot.add(cloned)\n // URDF의 base_link는 관행상 z=0이 바닥(floor origin). things-scene는 center\n // origin이므로, components3D 그룹(= -geometricOffsetY만큼 shift된 바닥 기준\n // subgroup)에 pivot을 넣어 로봇이 컴포넌트 바닥면에 서도록 한다.\n this.components3D.add(this.pivot)\n\n // 조인트 인덱스/메타 구축\n this._buildJointIndex(cloned)\n\n // bounding box 계산 후 updateDimension으로 크기 반영\n const box = new THREE.Box3().setFromObject(this.pivot)\n this._objectSize = box.getSize(new THREE.Vector3())\n\n this.updateDimension()\n\n // 자동 placement 정렬: URDF의 base_link 원점이 로봇 기하의 최저점이 아닐 때\n // (Nao/minitaur/quadrotor는 torso/중심 기준) 로봇 일부가 컴포넌트 범위를\n // 벗어나는 것을 방지. 씬의 placement 모드에 따라 정렬 기준이 다르다.\n // - floor: 로봇 최저점 → 컴포넌트 바닥면\n // - inverted: 로봇 최상점 → 컴포넌트 천장면 (매달림)\n // - space: 정렬 없음 (중심 원점 유지)\n // state.floorAlign === false 로 비활성화 가능.\n if (this.component.state.floorAlign !== false) {\n const placement = this.scenePlacement\n if (placement !== 'space') {\n this.components3D.updateMatrixWorld(true)\n const worldBox = new THREE.Box3().setFromObject(this.pivot)\n if (isFinite(worldBox.min.y) && isFinite(worldBox.max.y)) {\n if (placement === 'inverted') {\n const localMax = this.components3D.worldToLocal(worldBox.max.clone())\n if (localMax.y > 0) {\n this.pivot.position.y -= localMax.y\n }\n } else {\n // floor (기본)\n const localMin = this.components3D.worldToLocal(worldBox.min.clone())\n if (localMin.y < 0) {\n this.pivot.position.y -= localMin.y\n }\n }\n }\n }\n }\n\n // 컨트롤러 초기화 (state.physics 설정된 경우)\n const physicsMode = this.component.state.physics as string | undefined\n if (physicsMode && physicsMode !== 'none') {\n this._setupController(physicsMode)\n }\n\n // 초기 joint 상태 적용\n const jointStates = this.component.state.joints as Record<string, URDFJointState | number> | undefined\n if (jointStates) {\n this._dispatchJointStates(jointStates)\n }\n\n // 자동 애니메이션 시작 (state.autoAnimate === 'sine'인 경우)\n if (this.component.state.autoAnimate === 'sine') {\n this._startAutoAnimate()\n }\n\n // 탑뷰 스냅샷\n const source = this.component.state.src as string\n if (source && !RealObjectURDF._topViewCache.has(source)) {\n try {\n const canvas = RealObjectGLTF.renderTopView(this.pivot)\n RealObjectURDF._topViewCache.set(source, canvas)\n ;(this.component as any)._topViewSnapshot = canvas\n } catch {\n // 스냅샷 실패는 치명적이지 않음\n }\n } else if (source) {\n ;(this.component as any)._topViewSnapshot = RealObjectURDF._topViewCache.get(source)\n }\n }\n\n private _resolveUnitScale(): number {\n const s = this.component.state.unitScale\n if (typeof s === 'number' && s > 0) return s\n // 기본: URDF meter → 에디터 mm 스케일 1000\n return 1000\n }\n\n /**\n * 조인트 트리를 순회하며 인덱스와 메타를 구축한다.\n * fixed 조인트는 조작 대상이 아니므로 메타에서 제외하되 인덱스에는 남긴다.\n */\n private _buildJointIndex(robot: URDFRobot) {\n this._jointIndex.clear()\n this._jointMeta.clear()\n this._jointOriginals.clear()\n\n const joints = robot.joints || {}\n for (const [name, joint] of Object.entries(joints)) {\n this._jointIndex.set(name, joint)\n this._jointOriginals.set(name, joint.angle || 0)\n\n if (joint.jointType === 'fixed') continue\n\n this._jointMeta.set(name, {\n name,\n type: joint.jointType,\n axis: { x: joint.axis.x, y: joint.axis.y, z: joint.axis.z },\n limit: { ...joint.limit }\n })\n }\n }\n\n /**\n * state.joints 변경을 적절한 경로로 디스패치한다.\n * - controller가 활성이면 setTargets (controller가 스무딩/물리 적용)\n * - 없으면 즉시 적용 (direct 모드, 기본)\n */\n private _dispatchJointStates(states: Record<string, URDFJointState | number>) {\n if (this._controller) {\n const targets: Record<string, number> = {}\n for (const [name, raw] of Object.entries(states)) {\n const v = typeof raw === 'number' ? raw : Number((raw as { value?: number } | null)?.value)\n if (Number.isFinite(v)) targets[name] = v\n }\n this._controller.setTargets(targets)\n return\n }\n this._applyJointStates(states)\n }\n\n /**\n * Controller를 인스턴스화하고 setup + tick 루프 시작.\n * 이미 활성 중이면 먼저 해제.\n */\n private _setupController(mode: string) {\n this._disposeController()\n const ctrl = createJointController(mode)\n if (!ctrl) {\n warn(`URDFObject: unknown physics mode '${mode}'`)\n return\n }\n if (!this._robot) return\n\n ctrl.setup({ robot: this._robot, joints: this._jointIndex, jointMeta: this._jointMeta })\n this._controller = ctrl\n\n // 현재 state.joints를 초기 타겟으로 설정\n const s = this.component.state.joints as Record<string, URDFJointState | number> | undefined\n if (s) {\n const targets: Record<string, number> = {}\n for (const [name, raw] of Object.entries(s)) {\n const v = typeof raw === 'number' ? raw : Number((raw as { value?: number } | null)?.value)\n if (Number.isFinite(v)) targets[name] = v\n }\n ctrl.setTargets(targets)\n }\n\n this._ctrlLastTime = performance.now()\n const tick = () => {\n if (!this._controller) {\n this._ctrlRaf = undefined\n return\n }\n const now = performance.now()\n const dt = (now - this._ctrlLastTime) / 1000\n this._ctrlLastTime = now\n const changed = this._controller.tick(dt)\n if (changed) {\n this.component?.invalidate?.()\n }\n this._ctrlRaf = requestAnimationFrame(tick)\n }\n this._ctrlRaf = requestAnimationFrame(tick)\n }\n\n private _disposeController() {\n if (this._ctrlRaf != null) {\n cancelAnimationFrame(this._ctrlRaf)\n this._ctrlRaf = undefined\n }\n if (this._controller) {\n this._controller.dispose()\n this._controller = undefined\n }\n }\n\n /**\n * state.joints 맵을 순회하며 각 조인트 값을 적용한다 (direct 모드).\n * 값은 숫자 또는 { value: number } 형식 모두 허용 (유연성).\n *\n * joint transform 변경 후 component.invalidate()로 재렌더를 요청한다.\n * 표준 경로: invalidate → Layer.throttle_render → rAF __draw__ → trigger('redraw')\n * → ThreeCapability.animate → renderThreeD.\n */\n private _applyJointStates(states: Record<string, URDFJointState | number>) {\n if (!this._robot) return\n\n let changed = false\n for (const [name, raw] of Object.entries(states)) {\n const joint = this._jointIndex.get(name)\n if (!joint || joint.jointType === 'fixed') continue\n\n const value = typeof raw === 'number' ? raw : raw?.value\n if (typeof value !== 'number' || !isFinite(value)) continue\n\n joint.setJointValue(value)\n changed = true\n }\n\n if (changed) {\n this.component?.invalidate?.()\n }\n }\n\n /**\n * 자동 sine 애니메이션 시작.\n * 각 non-fixed 조인트를 limit 범위 내에서 sine 스윙. 조인트 순서에 따라\n * frequency와 phase를 약간씩 다르게 해 동기화를 피하고 유기적 움직임을 만든다.\n *\n * state.joints를 건드리지 않고 joint.setJointValue만 직접 호출 — state 변경\n * cascade를 피해 성능을 유지하고, 슬라이더가 있는 경우에도 state와 시각이\n * 분리되어 표시됨 (활성 시 state는 의미 없음).\n */\n private _startAutoAnimate() {\n if (this._animRaf != null) return\n if (!this._robot || this._jointMeta.size === 0) return\n\n this._animStartTime = performance.now()\n const TWO_PI = Math.PI * 2\n\n const tick = () => {\n if (!this._robot) {\n this._animRaf = undefined\n return\n }\n const t = (performance.now() - this._animStartTime) / 1000\n\n let i = 0\n let changed = false\n for (const [name, meta] of this._jointMeta) {\n const joint = this._jointIndex.get(name)\n if (!joint) continue\n\n const mid = (meta.limit.upper + meta.limit.lower) / 2\n const amp = (meta.limit.upper - meta.limit.lower) / 2\n // 조인트별로 frequency와 phase 약간씩 다르게 (0.15 ~ 0.4 Hz 범위)\n const freq = 0.15 + (i % 6) * 0.05\n const phase = i * 0.73\n const value = mid + amp * Math.sin(t * freq * TWO_PI + phase)\n joint.setJointValue(value)\n changed = true\n i++\n }\n\n if (changed) {\n this.component?.invalidate?.()\n }\n this._animRaf = requestAnimationFrame(tick)\n }\n this._animRaf = requestAnimationFrame(tick)\n }\n\n private _stopAutoAnimate() {\n if (this._animRaf != null) {\n cancelAnimationFrame(this._animRaf)\n this._animRaf = undefined\n }\n }\n\n /**\n * 모든 조인트를 원본 값으로 복원한다.\n */\n private _resetAllJoints() {\n if (!this._robot) return\n for (const [name, orig] of this._jointOriginals) {\n const joint = this._jointIndex.get(name)\n if (joint && joint.jointType !== 'fixed') {\n joint.setJointValue(orig)\n }\n }\n }\n\n // --- 라이프사이클 ---\n\n clear() {\n this._stopAutoAnimate()\n this._disposeController()\n this._jointIndex.clear()\n this._jointMeta.clear()\n this._jointOriginals.clear()\n this._robot = undefined\n\n return super.clear()\n }\n\n updateDimension() {\n if (!this.pivot) return\n\n const { width = 1, height = 1, depth = 1 } = this.component.state\n const { x = 1, y = 1, z = 1 } = this._objectSize || {}\n\n // URDF는 관절 기반 로봇의 비율을 유지해야 하므로 uniform scale을 적용한다.\n // 세 축 각각의 비율 중 가장 작은 값(best-fit)을 사용해 로봇이 컴포넌트의\n // bounding box 안에 왜곡 없이 맞춰지도록 한다.\n const unit = this._resolveUnitScale()\n const baseX = x || 1\n const baseY = y || 1\n const baseZ = z || 1\n const upAxis = this.component.state.upAxis\n const rotated = upAxis !== 'y'\n\n const rX = width / baseX\n const rY = (rotated ? depth : height) / baseY\n const rZ = (rotated ? height : depth) / baseZ\n const ratio = Math.min(rX, rY, rZ)\n\n this.pivot.scale.setScalar(ratio * unit)\n\n this.component.invalidate()\n }\n\n onchange(after: Properties, before: Properties) {\n super.onchange(after, before)\n\n if ('src' in after) {\n this.updateSource()\n return\n }\n\n if ('joints' in after) {\n // 사용자가 joint를 직접 조작하면 자동 애니메이션이 이를 즉시 덮어써\n // 의도를 상쇄하므로, 자동 애니메이션을 우선 중지한다.\n if (this._animRaf != null) {\n this._stopAutoAnimate()\n this._component.setState({ autoAnimate: 'none' })\n }\n const jointStates = after.joints as Record<string, URDFJointState | number> | undefined\n if (this._controller) {\n // controller 모드: 타겟만 갱신. 이전 값 리셋은 controller가 연속 감쇠로\n // 알아서 수렴하므로 불필요.\n if (jointStates && Object.keys(jointStates).length > 0) {\n this._dispatchJointStates(jointStates)\n }\n } else {\n // direct 모드: 전체 reset 후 명시된 값만 재적용 (sparse state 지원).\n this._resetAllJoints()\n if (jointStates && Object.keys(jointStates).length > 0) {\n this._applyJointStates(jointStates)\n }\n }\n }\n\n if ('autoAnimate' in after) {\n if (after.autoAnimate === 'sine') {\n this._startAutoAnimate()\n } else {\n this._stopAutoAnimate()\n }\n }\n\n if ('physics' in after) {\n const mode = after.physics as string | undefined\n if (mode && mode !== 'none') {\n this._setupController(mode)\n } else {\n this._disposeController()\n }\n }\n\n if ('upAxis' in after || 'unitScale' in after || 'packages' in after) {\n // 좌표/스케일/패키지 매핑 변경은 pivot 재구성이 필요하므로 전체 재빌드\n this.build()\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { JointController, JointControllerContext } from './joint-controller.js';
|
|
2
|
+
export declare class SmoothingController implements JointController {
|
|
3
|
+
private _joints?;
|
|
4
|
+
private _current;
|
|
5
|
+
private _target;
|
|
6
|
+
private _velocity;
|
|
7
|
+
/** 응답 주파수 (rad/s). 클수록 빠른 수렴. */
|
|
8
|
+
omega: number;
|
|
9
|
+
/** 수렴 판정 임계값. error와 velocity가 모두 이 값 미만이면 그 조인트는 skip. */
|
|
10
|
+
epsilon: number;
|
|
11
|
+
setup(ctx: JointControllerContext): void;
|
|
12
|
+
setTargets(targets: Record<string, number>): void;
|
|
13
|
+
tick(dt: number): boolean;
|
|
14
|
+
dispose(): void;
|
|
15
|
+
}
|