@konitif/viewer-3d 0.285.4 → 0.285.6
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 +40 -0
- package/dist/renderer/IncarnationMaterialProjection.d.ts +16 -0
- package/dist/renderer/IncarnationMaterialProjection.d.ts.map +1 -0
- package/dist/renderer/IncarnationMaterialProjection.js +45 -0
- package/dist/renderer/IncarnationMaterialProjection.js.map +1 -0
- package/dist/renderer/ObservedRobotPresentationBuffer.d.ts +15 -4
- package/dist/renderer/ObservedRobotPresentationBuffer.d.ts.map +1 -1
- package/dist/renderer/ObservedRobotPresentationBuffer.js +136 -8
- package/dist/renderer/ObservedRobotPresentationBuffer.js.map +1 -1
- package/dist/renderer/ObservedSoleStabilization.d.ts +41 -0
- package/dist/renderer/ObservedSoleStabilization.d.ts.map +1 -0
- package/dist/renderer/ObservedSoleStabilization.js +140 -0
- package/dist/renderer/ObservedSoleStabilization.js.map +1 -0
- package/dist/renderer/Viewer3DRenderer.d.ts +31 -0
- package/dist/renderer/Viewer3DRenderer.d.ts.map +1 -1
- package/dist/renderer/Viewer3DRenderer.js +331 -18
- package/dist/renderer/Viewer3DRenderer.js.map +1 -1
- package/dist/renderer/index.d.ts +1 -0
- package/dist/renderer/index.d.ts.map +1 -1
- package/dist/renderer/index.js +1 -0
- package/dist/renderer/index.js.map +1 -1
- package/dist/renderer/robotViewerMotion.d.ts +10 -0
- package/dist/renderer/robotViewerMotion.d.ts.map +1 -1
- package/dist/renderer/robotViewerMotion.js +19 -0
- package/dist/renderer/robotViewerMotion.js.map +1 -1
- package/dist/supportDefinition.d.ts +5 -0
- package/dist/supportDefinition.d.ts.map +1 -1
- package/package.json +1 -1
- package/reference/catalog.json +3 -0
|
@@ -5,6 +5,7 @@ import { resolvePhysicsSourceString, resolvePhysicsSourceStringListMap, resolveP
|
|
|
5
5
|
import { createClockGraph, createInvalidationController, createTemporalTransport } from '@konitif/temporal';
|
|
6
6
|
import { createViewerTemporalScheduler } from './ViewerTemporalScheduler.js';
|
|
7
7
|
import { copyViewerMetadata } from './ViewerMetadata.js';
|
|
8
|
+
import { IncarnationMaterialProjector, normalizeIncarnationMaterialProjection } from './IncarnationMaterialProjection.js';
|
|
8
9
|
import { resolveInteractionInputActions, resolveInteractionInputBindings } from '@konitif/tools/input';
|
|
9
10
|
import { AxesHelper, Box3, BoxGeometry, BufferGeometry, CanvasTexture, Color, ConeGeometry, CylinderGeometry, DirectionalLight, DoubleSide, Float32BufferAttribute, FogExp2, GridHelper, Group, HalfFloatType, HemisphereLight, Line, LineBasicMaterial, LineSegments, Matrix4, MathUtils, Mesh, MeshBasicMaterial, MeshDepthMaterial, NoToneMapping, Object3D, OrthographicCamera, Plane, PlaneGeometry, PerspectiveCamera, VSMShadowMap, Quaternion, Raycaster, Scene, CircleGeometry, SphereGeometry, Sprite, SpriteMaterial, SRGBColorSpace, Vector2, Vector3, WebGLRenderTarget, WebGLRenderer } from 'three';
|
|
10
11
|
import { ArcballControls } from 'three/examples/jsm/controls/ArcballControls.js';
|
|
@@ -17,7 +18,7 @@ import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
|
|
|
17
18
|
import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js';
|
|
18
19
|
import { HorizontalTiltShiftShader } from 'three/examples/jsm/shaders/HorizontalTiltShiftShader.js';
|
|
19
20
|
import { VerticalTiltShiftShader } from 'three/examples/jsm/shaders/VerticalTiltShiftShader.js';
|
|
20
|
-
import { getRobotMotionAnimationDuration, interpolateRobotMotionTrackValueAtTime, toRobotMotionJointValue, resolveRobotMotionPlaybackDuration } from './robotViewerMotion.js';
|
|
21
|
+
import { getRobotMotionAnimationDuration, interpolateRobotMotionTrackValueAtTime, sampleRobotMotionJointValuesAtTime, toRobotMotionJointValue, resolveRobotMotionPlaybackDuration } from './robotViewerMotion.js';
|
|
21
22
|
import { createViewerProjectileLaunchSolution, VIEWER3D_PROJECTILE_PARTICLE_LIMIT, createViewerProjectileTrajectoryPoints, resolveViewerProjectileChargedLaunchProfile, resolveViewerProjectileImpulse, resolveViewerProjectileLauncherWorldScale, resolveViewerSlingshotReleasePull, stepViewerProjectileAgainstGround } from './ProjectileInteraction.js';
|
|
22
23
|
import { createInteractionKeyboardSignal, DEFAULT_VIEWER_INPUT_BINDINGS, VIEWER_PROJECTILE_CHARGE_ACTION_ID, VIEWER_PROJECTILE_LAUNCH_ACTION_ID } from './ViewerInputBindings.js';
|
|
23
24
|
import { ViewerProjectileParticleRenderer } from './ViewerProjectileParticleRenderer.js';
|
|
@@ -30,6 +31,8 @@ import { resolveViewerIncarnationHealth } from './ViewerIncarnationHealth.js';
|
|
|
30
31
|
import { PerformanceMonitor } from './PerformanceMonitor.js';
|
|
31
32
|
import { ViewerTemporalProjectionBuffer } from './TemporalProjectionModel.js';
|
|
32
33
|
import { ObservedRobotPresentationBuffer } from './ObservedRobotPresentationBuffer.js';
|
|
34
|
+
import { ObservedSoleAnchors, projectBoundedSoleCorrection } from './ObservedSoleStabilization.js';
|
|
35
|
+
import { ConvexHull } from 'three/examples/jsm/math/ConvexHull.js';
|
|
33
36
|
import { resolveViewerFrameSignalIntervalMs, VIEWER_FRAME_SIGNAL_CATALOG } from './ViewerFrameSignals.js';
|
|
34
37
|
import { DEFAULT_DISPLAY_FRAME_RATE_HZ, normalizeConfiguredDisplayFrameRate, observeDisplayFrameRateCeiling, resolveDisplayFrameRateTarget } from './displayFrameRate.js';
|
|
35
38
|
import { ViewerTemporalProjectionExperimentAdapter } from './ViewerTemporalProjectionExperimentAdapter.js';
|
|
@@ -219,6 +222,8 @@ export class Viewer3D {
|
|
|
219
222
|
poseControlTarget = 'simulated';
|
|
220
223
|
observedPoseLiveControlEnabled = false;
|
|
221
224
|
robotMaterialBaselines = new Map();
|
|
225
|
+
incarnationMaterialProjector = new IncarnationMaterialProjector();
|
|
226
|
+
incarnationMaterialProjections = {};
|
|
222
227
|
observedRobotGhostMaterialBaselines = new Map();
|
|
223
228
|
observedRobotGhostRenderOrderBaselines = new Map();
|
|
224
229
|
observedRobotGhostShadowMaterials = new Map();
|
|
@@ -229,6 +234,11 @@ export class Viewer3D {
|
|
|
229
234
|
observedRobotGhostBaseQuaternion = new Quaternion();
|
|
230
235
|
observedRobotGhostBaseScale = new Vector3(1, 1, 1);
|
|
231
236
|
observedRobotTemporalSupportAnchorState = null;
|
|
237
|
+
observedSoleAnchors = new ObservedSoleAnchors();
|
|
238
|
+
observedSoleConnectionId = '';
|
|
239
|
+
observedSoleStrength = 0;
|
|
240
|
+
observedSoleBaseline = new Map();
|
|
241
|
+
observedSoleSurfaceVertices = new WeakMap();
|
|
232
242
|
observedRobotGrounding = resolveObservedRobotGroundingProjection({
|
|
233
243
|
enabled: false,
|
|
234
244
|
floorY: ROBOT_VIEWER_SUPPORT_FLOOR_Y,
|
|
@@ -296,6 +306,7 @@ export class Viewer3D {
|
|
|
296
306
|
projectilePointerInside = false;
|
|
297
307
|
trajectoryAnimation = null;
|
|
298
308
|
trajectorySignature = '';
|
|
309
|
+
trajectoryProjectionRevision = 0;
|
|
299
310
|
trajectoryAnchors = new Map();
|
|
300
311
|
themeObserver = null;
|
|
301
312
|
animationLoop;
|
|
@@ -1050,11 +1061,17 @@ export class Viewer3D {
|
|
|
1050
1061
|
sampleId: input.frameId,
|
|
1051
1062
|
connectionId: input.connectionId,
|
|
1052
1063
|
sourceObservedAtSeconds: input.observedAtSeconds,
|
|
1064
|
+
sourceClock: input.sourceClock,
|
|
1053
1065
|
receivedAtMs,
|
|
1054
1066
|
jointValues: input.jointValues
|
|
1055
1067
|
});
|
|
1056
1068
|
if (!accepted)
|
|
1057
1069
|
return false;
|
|
1070
|
+
if (this.observedSoleConnectionId !== input.connectionId) {
|
|
1071
|
+
this.restoreObservedSoleBaseline();
|
|
1072
|
+
this.observedSoleAnchors.reset();
|
|
1073
|
+
this.observedSoleConnectionId = input.connectionId;
|
|
1074
|
+
}
|
|
1058
1075
|
const frame = this.observedRobotPresentation.project(receivedAtMs);
|
|
1059
1076
|
if (frame)
|
|
1060
1077
|
this.applyObservedRobotGhostJointValues(frame.jointValues);
|
|
@@ -1062,14 +1079,35 @@ export class Viewer3D {
|
|
|
1062
1079
|
return true;
|
|
1063
1080
|
}
|
|
1064
1081
|
resetObservedRobotGhostJointObservation() {
|
|
1082
|
+
this.restoreObservedSoleBaseline();
|
|
1083
|
+
this.observedSoleAnchors.reset();
|
|
1065
1084
|
this.observedRobotPresentation.reset('discontinuity');
|
|
1066
1085
|
}
|
|
1086
|
+
setObservedRobotSoleStabilizationStrength(value) {
|
|
1087
|
+
this.observedSoleAnchors ??= new ObservedSoleAnchors();
|
|
1088
|
+
const strength = Number.isFinite(value) ? Math.max(0, Math.min(1, value)) : 0;
|
|
1089
|
+
if (strength === this.observedSoleStrength)
|
|
1090
|
+
return;
|
|
1091
|
+
this.restoreObservedSoleBaseline();
|
|
1092
|
+
this.observedSoleAnchors.reset();
|
|
1093
|
+
this.observedSoleStrength = strength;
|
|
1094
|
+
this.applyObservedRobotGhostGrounding();
|
|
1095
|
+
this.requestRender();
|
|
1096
|
+
}
|
|
1097
|
+
restoreObservedSoleBaseline() {
|
|
1098
|
+
this.observedSoleBaseline ??= new Map();
|
|
1099
|
+
for (const [joint, value] of this.observedSoleBaseline)
|
|
1100
|
+
joint.setJointValue(value);
|
|
1101
|
+
this.observedSoleBaseline.clear();
|
|
1102
|
+
}
|
|
1067
1103
|
getObservedRobotPresentationStatus() {
|
|
1068
1104
|
return this.observedRobotPresentation.getStatus();
|
|
1069
1105
|
}
|
|
1070
1106
|
applyObservedRobotGhostJointValues(values) {
|
|
1071
1107
|
if (!this.observedRobotGhost)
|
|
1072
1108
|
return false;
|
|
1109
|
+
const hadSoleCorrection = (this.observedSoleBaseline?.size ?? 0) > 0;
|
|
1110
|
+
this.restoreObservedSoleBaseline();
|
|
1073
1111
|
const joints = this.observedRobotGhost.joints ?? {};
|
|
1074
1112
|
let changed = false;
|
|
1075
1113
|
for (const [target, value] of Object.entries(values)) {
|
|
@@ -1081,7 +1119,7 @@ export class Viewer3D {
|
|
|
1081
1119
|
continue;
|
|
1082
1120
|
changed = joint.setJointValue(value) !== false || changed;
|
|
1083
1121
|
}
|
|
1084
|
-
if (changed) {
|
|
1122
|
+
if (changed || hadSoleCorrection) {
|
|
1085
1123
|
this.applyObservedRobotGhostGrounding();
|
|
1086
1124
|
if (this.poseControlTarget === 'observed') {
|
|
1087
1125
|
this.refreshPoseHandleProjection();
|
|
@@ -1144,10 +1182,28 @@ export class Viewer3D {
|
|
|
1144
1182
|
// invalidate that anchor.
|
|
1145
1183
|
if (spatialLayoutChanged) {
|
|
1146
1184
|
this.observedRobotTemporalSupportAnchorState = null;
|
|
1185
|
+
this.observedSoleAnchors?.reset();
|
|
1147
1186
|
}
|
|
1148
1187
|
this.applyRobotComparisonAppearance();
|
|
1149
1188
|
this.requestRender();
|
|
1150
1189
|
}
|
|
1190
|
+
/** Tune one visual incarnation independently; source meshes and telemetry remain unchanged. */
|
|
1191
|
+
setIncarnationMaterialProjection(target, projection) {
|
|
1192
|
+
if (target !== 'simulated' && target !== 'observed')
|
|
1193
|
+
throw new Error('Invalid material incarnation target');
|
|
1194
|
+
this.incarnationMaterialProjections[target] = normalizeIncarnationMaterialProjection(projection);
|
|
1195
|
+
this.applyIncarnationMaterialProjections();
|
|
1196
|
+
this.requestRender();
|
|
1197
|
+
}
|
|
1198
|
+
applyIncarnationMaterialProjections() {
|
|
1199
|
+
for (const target of ['simulated', 'observed']) {
|
|
1200
|
+
const projection = this.incarnationMaterialProjections[target];
|
|
1201
|
+
if (!projection)
|
|
1202
|
+
continue;
|
|
1203
|
+
const materials = target === 'simulated' ? this.robotMaterialBaselines : this.observedRobotGhostMaterialBaselines;
|
|
1204
|
+
this.incarnationMaterialProjector.apply(materials.keys(), projection);
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1151
1207
|
async setPhysicsEngine(physicsEngine, physicsEnabled = physicsEngine !== 'none') {
|
|
1152
1208
|
if (this.destroyed)
|
|
1153
1209
|
return;
|
|
@@ -1178,6 +1234,7 @@ export class Viewer3D {
|
|
|
1178
1234
|
this.resetTemporalProjection();
|
|
1179
1235
|
if (this.options.physicsEnabled) {
|
|
1180
1236
|
this.physicsVisualRootToRobotMatrix = null;
|
|
1237
|
+
this.invalidateTrajectoryPreviewProjection();
|
|
1181
1238
|
if (!this.syncPhysicsKinematicPoseFromRobotPose({ holdFrames: PHYSICS_RESET_KINEMATIC_HOLD_FRAMES })) {
|
|
1182
1239
|
this.syncPhysicsJointTargetsFromRobotPose();
|
|
1183
1240
|
}
|
|
@@ -1203,6 +1260,7 @@ export class Viewer3D {
|
|
|
1203
1260
|
}
|
|
1204
1261
|
this.options.physicsMotorsCoupled = physicsMotorsCoupled;
|
|
1205
1262
|
this.physicsVisualRootToRobotMatrix = null;
|
|
1263
|
+
this.invalidateTrajectoryPreviewProjection();
|
|
1206
1264
|
this.resetPhysicsPostureCompensation();
|
|
1207
1265
|
if (!physicsMotorsCoupled) {
|
|
1208
1266
|
this.robot?.updateMatrixWorld(true);
|
|
@@ -1235,6 +1293,7 @@ export class Viewer3D {
|
|
|
1235
1293
|
resolvePhysicsSourceString(source.metadata?.visualRootBodyName);
|
|
1236
1294
|
this.physicsVisualBodyObjectNames = visualBodyObjectNames;
|
|
1237
1295
|
this.physicsVisualRootToRobotMatrix = null;
|
|
1296
|
+
this.invalidateTrajectoryPreviewProjection();
|
|
1238
1297
|
this.physicsResetPose = null;
|
|
1239
1298
|
this.physicsResetRobotTransform = null;
|
|
1240
1299
|
this.physicsResetBaselineCaptureFrames = 0;
|
|
@@ -1272,6 +1331,7 @@ export class Viewer3D {
|
|
|
1272
1331
|
this.scrubPreviewState = null;
|
|
1273
1332
|
this.playbackSyncSignature = '';
|
|
1274
1333
|
this.physicsVisualRootToRobotMatrix = null;
|
|
1334
|
+
this.invalidateTrajectoryPreviewProjection();
|
|
1275
1335
|
this.physicsAuthoredJointTargets.clear();
|
|
1276
1336
|
this.resetPhysicsPostureCompensation();
|
|
1277
1337
|
this.comparisonViewerSample = null;
|
|
@@ -1283,6 +1343,7 @@ export class Viewer3D {
|
|
|
1283
1343
|
this.lastPhysicsStepAt = 0;
|
|
1284
1344
|
this.resetTemporalProjection();
|
|
1285
1345
|
this.physicsVisualRootToRobotMatrix = null;
|
|
1346
|
+
this.invalidateTrajectoryPreviewProjection();
|
|
1286
1347
|
if (this.options.physicsMotorsCoupled) {
|
|
1287
1348
|
if (!this.syncPhysicsKinematicPoseFromRobotPose({ holdFrames: PHYSICS_RESET_KINEMATIC_HOLD_FRAMES })) {
|
|
1288
1349
|
this.syncPhysicsJointTargetsFromRobotPose();
|
|
@@ -2032,7 +2093,12 @@ export class Viewer3D {
|
|
|
2032
2093
|
}
|
|
2033
2094
|
setTrajectoryPreview(animation) {
|
|
2034
2095
|
if (this.trajectoryAnimation === animation) {
|
|
2035
|
-
this.
|
|
2096
|
+
if (this.options.showTrajectories) {
|
|
2097
|
+
this.rebuildTrajectoryPreview();
|
|
2098
|
+
}
|
|
2099
|
+
else {
|
|
2100
|
+
this.updateTrajectoryGroupVisibility();
|
|
2101
|
+
}
|
|
2036
2102
|
return;
|
|
2037
2103
|
}
|
|
2038
2104
|
this.trajectoryAnimation = animation;
|
|
@@ -2056,6 +2122,7 @@ export class Viewer3D {
|
|
|
2056
2122
|
this.captureMaterialBaselines(robot, this.robotMaterialBaselines);
|
|
2057
2123
|
this.resetTemporalProjection();
|
|
2058
2124
|
this.physicsVisualRootToRobotMatrix = null;
|
|
2125
|
+
this.invalidateTrajectoryPreviewProjection();
|
|
2059
2126
|
this.physicsProjectionMinimumStateRevision = null;
|
|
2060
2127
|
this.physicsResetPose = null;
|
|
2061
2128
|
this.physicsResetRobotTransform = null;
|
|
@@ -2385,6 +2452,9 @@ export class Viewer3D {
|
|
|
2385
2452
|
this.playbackState?.isPlaying &&
|
|
2386
2453
|
this.playbackState.animation === animation &&
|
|
2387
2454
|
this.playbackSyncSignature === syncSignature) {
|
|
2455
|
+
if (this.options.showTrajectories && animation) {
|
|
2456
|
+
this.rebuildTrajectoryPreview();
|
|
2457
|
+
}
|
|
2388
2458
|
return;
|
|
2389
2459
|
}
|
|
2390
2460
|
const nextPlaybackState = {
|
|
@@ -2438,6 +2508,7 @@ export class Viewer3D {
|
|
|
2438
2508
|
this.alignUnbufferedPlaybackPoseToSupportFloor();
|
|
2439
2509
|
}
|
|
2440
2510
|
}
|
|
2511
|
+
this.rebuildTrajectoryPreview();
|
|
2441
2512
|
this.updateTrajectoryCurrentMarkers();
|
|
2442
2513
|
if (nextPlaybackState.clockMode === 'realtime') {
|
|
2443
2514
|
this.scheduleLoop();
|
|
@@ -2467,6 +2538,7 @@ export class Viewer3D {
|
|
|
2467
2538
|
this.alignUnbufferedPlaybackPoseToSupportFloor();
|
|
2468
2539
|
}
|
|
2469
2540
|
}
|
|
2541
|
+
this.rebuildTrajectoryPreview();
|
|
2470
2542
|
this.updateTrajectoryCurrentMarkers();
|
|
2471
2543
|
if (shouldStartViewerPlaybackCompletionTransition({
|
|
2472
2544
|
isPlaying,
|
|
@@ -3971,12 +4043,12 @@ export class Viewer3D {
|
|
|
3971
4043
|
if (this.lastFrameAt > 0) {
|
|
3972
4044
|
const frameMs = now - this.lastFrameAt;
|
|
3973
4045
|
this.averageFrameMs = this.averageFrameMs * 0.9 + frameMs * 0.1;
|
|
3974
|
-
this.tunePixelRatio(this.averageFrameMs);
|
|
3975
4046
|
}
|
|
3976
4047
|
this.lastFrameAt = now;
|
|
3977
4048
|
const renderStartedAt = performance.now();
|
|
3978
4049
|
this.render();
|
|
3979
4050
|
this.averageRenderMs = this.updateProfilerAverage(this.averageRenderMs, performance.now() - renderStartedAt);
|
|
4051
|
+
this.tunePixelRatio(this.averageRenderMs);
|
|
3980
4052
|
this.lastRenderCalls = this.renderer.info.render.calls;
|
|
3981
4053
|
this.lastRenderTriangles = this.renderer.info.render.triangles;
|
|
3982
4054
|
this.renderFramesSinceReport += 1;
|
|
@@ -4031,6 +4103,16 @@ export class Viewer3D {
|
|
|
4031
4103
|
if (this.observedRobotGhostMirrorsSimulatedRobot) {
|
|
4032
4104
|
this.syncObservedRobotGhostFromSimulatedRobot();
|
|
4033
4105
|
}
|
|
4106
|
+
if (this.options.showTrajectories &&
|
|
4107
|
+
this.trajectoryAnimation?.motion.hasMotion &&
|
|
4108
|
+
this.trajectorySignature === '') {
|
|
4109
|
+
this.rebuildTrajectoryPreview();
|
|
4110
|
+
}
|
|
4111
|
+
// Every pose authority (authored playback, runtime-history replay and
|
|
4112
|
+
// physics projection) converges here. Refreshing the markers immediately
|
|
4113
|
+
// before drawing prevents a solver step or a root presentation offset
|
|
4114
|
+
// from leaving them one frame behind the rendered robot.
|
|
4115
|
+
this.updateTrajectoryCurrentMarkers();
|
|
4034
4116
|
this.updatePoseHandleScreenScale();
|
|
4035
4117
|
this.updateProjectileCameraProjection();
|
|
4036
4118
|
const ground = this.visualGroundLayer;
|
|
@@ -5123,6 +5205,10 @@ export class Viewer3D {
|
|
|
5123
5205
|
return;
|
|
5124
5206
|
const ghostMaterials = sourceMaterials.map((source) => {
|
|
5125
5207
|
const material = source.clone();
|
|
5208
|
+
// Three material cloning does not retain consumer-owned shader hooks.
|
|
5209
|
+
// Preserve the observed model's own hooks, never the simulated state.
|
|
5210
|
+
material.onBeforeCompile = source.onBeforeCompile;
|
|
5211
|
+
material.customProgramCacheKey = source.customProgramCacheKey;
|
|
5126
5212
|
this.captureMaterialBaseline(material, this.observedRobotGhostMaterialBaselines);
|
|
5127
5213
|
return material;
|
|
5128
5214
|
});
|
|
@@ -5162,6 +5248,8 @@ export class Viewer3D {
|
|
|
5162
5248
|
}
|
|
5163
5249
|
}
|
|
5164
5250
|
clearObservedRobotGhost() {
|
|
5251
|
+
this.restoreObservedSoleBaseline();
|
|
5252
|
+
this.observedSoleAnchors?.reset();
|
|
5165
5253
|
this.cancelRobotComparisonVisibilityTransition();
|
|
5166
5254
|
this.observedRobotPresentation.reset('discontinuity');
|
|
5167
5255
|
this.simulatedRobotPresentationRoot.position.x = 0;
|
|
@@ -5205,6 +5293,7 @@ export class Viewer3D {
|
|
|
5205
5293
|
this.applyBalanceDebugPresentationOffset();
|
|
5206
5294
|
}
|
|
5207
5295
|
applyRobotComparisonAppearance() {
|
|
5296
|
+
this.applyIncarnationMaterialProjections();
|
|
5208
5297
|
this.applyMaterialAppearance(this.robotMaterialBaselines, this.robotComparisonAppearance.simulated);
|
|
5209
5298
|
this.applyMaterialAppearance(this.observedRobotGhostMaterialBaselines, this.robotComparisonAppearance.observed);
|
|
5210
5299
|
this.applyObservedRobotGhostRenderOrder(this.robotComparisonAppearance.observed.opacity < 0.999);
|
|
@@ -5241,6 +5330,7 @@ export class Viewer3D {
|
|
|
5241
5330
|
}
|
|
5242
5331
|
}
|
|
5243
5332
|
applyObservedRobotGhostGrounding() {
|
|
5333
|
+
this.restoreObservedSoleBaseline();
|
|
5244
5334
|
if (this.comparisonProfileId) {
|
|
5245
5335
|
this.projectComparisonSimulation();
|
|
5246
5336
|
return;
|
|
@@ -5268,6 +5358,12 @@ export class Viewer3D {
|
|
|
5268
5358
|
robot.updateMatrixWorld(true);
|
|
5269
5359
|
const supportContacts = this.resolveObservedRobotSupportContacts(robot);
|
|
5270
5360
|
const supportPoints = supportContacts.supportPoints;
|
|
5361
|
+
const lowestRawSupport = Math.min(...supportPoints.map(point => point.y));
|
|
5362
|
+
const soleEligible = new Map();
|
|
5363
|
+
for (const group of new Set(supportContacts.supportGroupIds)) {
|
|
5364
|
+
const minimum = Math.min(...supportPoints.filter((_, index) => supportContacts.supportGroupIds[index] === group).map(point => point.y));
|
|
5365
|
+
soleEligible.set(group, minimum - lowestRawSupport <= 0.008);
|
|
5366
|
+
}
|
|
5271
5367
|
const activeSupport = resolveObservedRobotActiveSupportProjection({
|
|
5272
5368
|
supportPoints,
|
|
5273
5369
|
contactIds: supportContacts.contactIds,
|
|
@@ -5316,18 +5412,134 @@ export class Viewer3D {
|
|
|
5316
5412
|
robot.position.x += temporalSupportAnchor.offsetX;
|
|
5317
5413
|
robot.position.z += temporalSupportAnchor.offsetZ;
|
|
5318
5414
|
robot.updateMatrixWorld(true);
|
|
5415
|
+
const stabilizedSurfaceMinimumY = this.applyObservedSoleStabilization(robot, soleEligible, footSupportEligibility.eligible);
|
|
5319
5416
|
const observedSupport = this.resolveObservedRobotSupport(robot, activeSupport.activeContactCount);
|
|
5320
5417
|
this.observedRobotGrounding = resolveObservedRobotGroundingProjection({
|
|
5321
5418
|
enabled: this.options.groundObservedRobotGhost,
|
|
5322
5419
|
floorY: ROBOT_VIEWER_SUPPORT_FLOOR_Y,
|
|
5323
|
-
supportMinimumY: observedSupport.supportMinimumY,
|
|
5420
|
+
supportMinimumY: stabilizedSurfaceMinimumY ?? observedSupport.supportMinimumY,
|
|
5324
5421
|
supportContactCount: observedSupport.supportContactCount,
|
|
5325
|
-
source: observedSupport.source
|
|
5422
|
+
source: stabilizedSurfaceMinimumY === null || stabilizedSurfaceMinimumY === undefined ? observedSupport.source : 'foot-geometry'
|
|
5326
5423
|
});
|
|
5327
5424
|
robot.position.y += this.observedRobotGrounding.offsetY;
|
|
5328
5425
|
robot.updateMatrixWorld(true);
|
|
5329
5426
|
this.applyNavigationPreviewPresentationTransform();
|
|
5330
5427
|
}
|
|
5428
|
+
applyObservedSoleStabilization(robot, eligible, supportEligible) {
|
|
5429
|
+
if (!(this.observedSoleStrength > 0) || !supportEligible)
|
|
5430
|
+
return null;
|
|
5431
|
+
const status = this.observedRobotPresentation?.getStatus();
|
|
5432
|
+
if (status?.mode === 'raw' || status?.fallbackReason === 'sample_gap') {
|
|
5433
|
+
this.observedSoleAnchors.reset();
|
|
5434
|
+
return null;
|
|
5435
|
+
}
|
|
5436
|
+
const chains = this.options.supportDefinition?.soleStabilizationChains ?? [];
|
|
5437
|
+
const joints = robot.joints ?? {};
|
|
5438
|
+
const pose = (groupId, foot) => {
|
|
5439
|
+
const contacts = this.resolveObservedRobotSupportContacts(robot);
|
|
5440
|
+
const points = contacts.supportPoints.filter((_, index) => contacts.supportGroupIds[index] === groupId);
|
|
5441
|
+
const center = points.reduce((sum, point) => sum.add(point), new Vector3()).multiplyScalar(1 / Math.max(1, points.length));
|
|
5442
|
+
const rotation = foot.getWorldQuaternion(new Quaternion());
|
|
5443
|
+
const supportMinimumY = this.resolvePreciseObservedSoleMinimumY(foot) ?? Math.min(...points.map(point => point.y));
|
|
5444
|
+
return { position: [center.x, center.y, center.z], orientation: [rotation.x, rotation.y, rotation.z, rotation.w], supportMinimumY };
|
|
5445
|
+
};
|
|
5446
|
+
const candidates = chains.flatMap(chain => {
|
|
5447
|
+
const selected = chain.jointNames.map(name => joints[name]);
|
|
5448
|
+
const foot = selected[selected.length - 1];
|
|
5449
|
+
if (!foot || typeof foot.getWorldQuaternion !== 'function' || selected.some(joint => !joint || joint.mimicJoint))
|
|
5450
|
+
return [];
|
|
5451
|
+
const baseline = selected.map(joint => this.readJointValue(joint));
|
|
5452
|
+
if (baseline.some(value => typeof value !== 'number' || !Number.isFinite(value)))
|
|
5453
|
+
return [];
|
|
5454
|
+
return [{ chain, selected: selected, foot, baseline: baseline, raw: pose(chain.groupId, foot) }];
|
|
5455
|
+
});
|
|
5456
|
+
const targets = this.observedSoleAnchors.resolve(this.observedSoleConnectionId, candidates.map(candidate => ({
|
|
5457
|
+
id: candidate.chain.groupId, position: candidate.raw.position,
|
|
5458
|
+
eligible: this.options.groundObservedRobotGhost && supportEligible && eligible.get(candidate.chain.groupId) === true
|
|
5459
|
+
})), this.observedSoleStrength);
|
|
5460
|
+
// Both eligible soles share a surface before the final root grounding.
|
|
5461
|
+
// Preserve lifted/released feet rather than treating every chain as contact.
|
|
5462
|
+
const supportPlaneY = Math.min(...candidates.filter(candidate => targets.some(target => target.id === candidate.chain.groupId)).map(candidate => candidate.raw.supportMinimumY));
|
|
5463
|
+
for (const target of targets) {
|
|
5464
|
+
const candidate = candidates.find(entry => entry.chain.groupId === target.id);
|
|
5465
|
+
const verticalOffset = supportPlaneY - candidate.raw.supportMinimumY;
|
|
5466
|
+
const targetPosition = [target.target[0], candidate.raw.position[1] + verticalOffset, target.target[2]];
|
|
5467
|
+
if (Math.hypot(...targetPosition.map((value, index) => value - candidate.raw.position[index])) < 0.00001)
|
|
5468
|
+
continue;
|
|
5469
|
+
const apply = (values) => {
|
|
5470
|
+
candidate.selected.forEach((joint, index) => joint.setJointValue(values[index]));
|
|
5471
|
+
robot.updateMatrixWorld(true);
|
|
5472
|
+
};
|
|
5473
|
+
try {
|
|
5474
|
+
const correction = projectBoundedSoleCorrection({
|
|
5475
|
+
values: candidate.baseline,
|
|
5476
|
+
limits: candidate.selected.map(joint => [joint.limit?.lower ?? -Math.PI, joint.limit?.upper ?? Math.PI]),
|
|
5477
|
+
targetPosition, targetOrientation: candidate.raw.orientation,
|
|
5478
|
+
targetSupportMinimumY: supportPlaneY,
|
|
5479
|
+
evaluate: values => { apply(values); return pose(target.id, candidate.foot); }
|
|
5480
|
+
});
|
|
5481
|
+
apply(correction.values);
|
|
5482
|
+
candidate.selected.forEach((joint, index) => this.observedSoleBaseline.set(joint, candidate.baseline[index]));
|
|
5483
|
+
}
|
|
5484
|
+
catch {
|
|
5485
|
+
// Invalid/unreachable authored chains must leave the observed copy intact.
|
|
5486
|
+
apply(candidate.baseline);
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5489
|
+
if (!targets.length)
|
|
5490
|
+
return null;
|
|
5491
|
+
// An unanchored foot must still not be pushed through the floor by root grounding.
|
|
5492
|
+
const visibleMinima = candidates.map(candidate => this.resolvePreciseObservedSoleMinimumY(candidate.foot))
|
|
5493
|
+
.filter((value) => value !== null);
|
|
5494
|
+
const minimum = Math.min(...visibleMinima);
|
|
5495
|
+
return Number.isFinite(minimum) ? minimum : null;
|
|
5496
|
+
}
|
|
5497
|
+
/** Exact directional support of rigid visible meshes, with cached convex vertices. */
|
|
5498
|
+
resolvePreciseObservedSoleMinimumY(foot) {
|
|
5499
|
+
this.observedSoleSurfaceVertices ??= new WeakMap();
|
|
5500
|
+
let minimum = Infinity;
|
|
5501
|
+
foot.traverse(node => {
|
|
5502
|
+
const mesh = node;
|
|
5503
|
+
if (!mesh.isMesh || mesh.isURDFCollider || !mesh.geometry)
|
|
5504
|
+
return;
|
|
5505
|
+
const attribute = mesh.geometry.getAttribute('position');
|
|
5506
|
+
if (!attribute)
|
|
5507
|
+
return;
|
|
5508
|
+
const version = 'version' in attribute ? Number(attribute.version) : attribute.data.version;
|
|
5509
|
+
let cached = this.observedSoleSurfaceVertices.get(mesh.geometry);
|
|
5510
|
+
if (!cached || cached.attribute !== attribute || cached.version !== version) {
|
|
5511
|
+
const points = [];
|
|
5512
|
+
for (let index = 0; index < attribute.count; index++) {
|
|
5513
|
+
const point = new Vector3().fromBufferAttribute(attribute, index);
|
|
5514
|
+
if ([point.x, point.y, point.z].every(Number.isFinite))
|
|
5515
|
+
points.push(point);
|
|
5516
|
+
}
|
|
5517
|
+
let vertices = points;
|
|
5518
|
+
if (points.length > 4) {
|
|
5519
|
+
try {
|
|
5520
|
+
const hull = new ConvexHull().setFromPoints(points);
|
|
5521
|
+
const unique = new Set();
|
|
5522
|
+
for (const face of hull.faces) {
|
|
5523
|
+
let edge = face.edge;
|
|
5524
|
+
do {
|
|
5525
|
+
unique.add(edge.vertex.point);
|
|
5526
|
+
edge = edge.next;
|
|
5527
|
+
} while (edge !== face.edge);
|
|
5528
|
+
}
|
|
5529
|
+
if (unique.size)
|
|
5530
|
+
vertices = [...unique];
|
|
5531
|
+
}
|
|
5532
|
+
catch { /* Degenerate meshes retain their exact original vertices. */ }
|
|
5533
|
+
}
|
|
5534
|
+
cached = { attribute, version, points: vertices };
|
|
5535
|
+
this.observedSoleSurfaceVertices.set(mesh.geometry, cached);
|
|
5536
|
+
}
|
|
5537
|
+
const matrix = mesh.matrixWorld.elements;
|
|
5538
|
+
for (const point of cached.points)
|
|
5539
|
+
minimum = Math.min(minimum, matrix[1] * point.x + matrix[5] * point.y + matrix[9] * point.z + matrix[13]);
|
|
5540
|
+
});
|
|
5541
|
+
return Number.isFinite(minimum) ? minimum : null;
|
|
5542
|
+
}
|
|
5331
5543
|
resolveObservedRobotSupportContacts(robot) {
|
|
5332
5544
|
const supportPoints = [];
|
|
5333
5545
|
const contactIds = [];
|
|
@@ -6029,6 +6241,7 @@ export class Viewer3D {
|
|
|
6029
6241
|
const previousPresentedY = this.robot.position.y + this.simulatedRobotPresentationRoot.position.y;
|
|
6030
6242
|
if (!this.physicsVisualRootToRobotMatrix) {
|
|
6031
6243
|
this.physicsVisualRootToRobotMatrix = this.resolvePhysicsVisualRootToRobotMatrix(physicsRootWorldMatrix, bodyTransforms);
|
|
6244
|
+
this.invalidateTrajectoryPreviewProjection();
|
|
6032
6245
|
}
|
|
6033
6246
|
const nextRobotWorldMatrix = physicsRootWorldMatrix.clone().multiply(this.physicsVisualRootToRobotMatrix);
|
|
6034
6247
|
const parent = this.robot.parent;
|
|
@@ -8420,6 +8633,7 @@ export class Viewer3D {
|
|
|
8420
8633
|
if (!rootTransform)
|
|
8421
8634
|
return;
|
|
8422
8635
|
this.physicsVisualRootToRobotMatrix = this.resolvePhysicsVisualRootToRobotMatrix(this.createPhysicsBodyViewerWorldMatrix(rootTransform), bodyTransforms);
|
|
8636
|
+
this.invalidateTrajectoryPreviewProjection();
|
|
8423
8637
|
}
|
|
8424
8638
|
projectProjectilePhysicsObservation() {
|
|
8425
8639
|
const bodyTransforms = this.physicsService.getBodyTransforms();
|
|
@@ -10315,6 +10529,10 @@ export class Viewer3D {
|
|
|
10315
10529
|
marker.name = 'balance-center-of-mass-ground-projection';
|
|
10316
10530
|
return marker;
|
|
10317
10531
|
}
|
|
10532
|
+
invalidateTrajectoryPreviewProjection() {
|
|
10533
|
+
this.trajectoryProjectionRevision += 1;
|
|
10534
|
+
this.trajectorySignature = '';
|
|
10535
|
+
}
|
|
10318
10536
|
ensureTrajectoryGroup() {
|
|
10319
10537
|
if (this.trajectoryGroup) {
|
|
10320
10538
|
return this.trajectoryGroup;
|
|
@@ -10322,7 +10540,7 @@ export class Viewer3D {
|
|
|
10322
10540
|
this.trajectoryGroup = new Group();
|
|
10323
10541
|
this.trajectoryGroup.name = 'AnimationTrajectoryPreview';
|
|
10324
10542
|
this.trajectoryGroup.visible = false;
|
|
10325
|
-
this.
|
|
10543
|
+
this.simulatedRobotPresentationRoot.add(this.trajectoryGroup);
|
|
10326
10544
|
return this.trajectoryGroup;
|
|
10327
10545
|
}
|
|
10328
10546
|
updateTrajectoryGroupVisibility() {
|
|
@@ -10371,18 +10589,31 @@ export class Viewer3D {
|
|
|
10371
10589
|
this.clearTrajectoryPreview();
|
|
10372
10590
|
this.trajectorySignature = nextSignature;
|
|
10373
10591
|
const originalPose = this.captureJointPose();
|
|
10592
|
+
const originalRobotTransform = this.captureRobotTransform(this.robot);
|
|
10593
|
+
const temporalSampleResolver = this.playbackState?.animation === animation
|
|
10594
|
+
? this.playbackState.resolveTemporalProjectionSample
|
|
10595
|
+
: null;
|
|
10596
|
+
let temporalRootAlignment = this.physicsVisualRootToRobotMatrix?.clone() ?? null;
|
|
10374
10597
|
const previousAnimatedTargets = new Set(this.lastAnimatedJointTargets);
|
|
10375
10598
|
const sampleCount = Math.max(ROBOT_VIEWER_TRAJECTORY_MIN_SAMPLES, Math.min(ROBOT_VIEWER_TRAJECTORY_MAX_SAMPLES, Math.ceil(duration * ROBOT_VIEWER_TRAJECTORY_SAMPLE_RATE)));
|
|
10376
10599
|
const pointsByTarget = new Map(targets.map((target) => [target.id, []]));
|
|
10377
10600
|
for (let sampleIndex = 0; sampleIndex <= sampleCount; sampleIndex += 1) {
|
|
10378
10601
|
const sampleTime = (sampleIndex / sampleCount) * duration;
|
|
10379
|
-
|
|
10602
|
+
const temporalSample = temporalSampleResolver?.(sampleTime) ?? null;
|
|
10603
|
+
this.restoreRobotTransform(this.robot, originalRobotTransform);
|
|
10604
|
+
if (temporalSample?.viewer) {
|
|
10605
|
+
temporalRootAlignment = this.applyTrajectoryTemporalSamplePose(temporalSample, temporalRootAlignment);
|
|
10606
|
+
}
|
|
10607
|
+
else {
|
|
10608
|
+
this.applyTrajectorySamplePose(animation, sampleTime, duration);
|
|
10609
|
+
}
|
|
10380
10610
|
this.robot.updateMatrixWorld(true);
|
|
10381
10611
|
for (const target of targets) {
|
|
10382
|
-
pointsByTarget.get(target.id)?.push(this.
|
|
10612
|
+
pointsByTarget.get(target.id)?.push(this.getTrajectoryPointInProjectionFrame(target.id));
|
|
10383
10613
|
}
|
|
10384
10614
|
}
|
|
10385
|
-
this.
|
|
10615
|
+
this.restoreRobotTransform(this.robot, originalRobotTransform);
|
|
10616
|
+
this.restoreTrajectorySamplePose(originalPose);
|
|
10386
10617
|
this.lastAnimatedJointTargets = previousAnimatedTargets;
|
|
10387
10618
|
this.nextAnimatedJointTargets.clear();
|
|
10388
10619
|
for (const target of targets) {
|
|
@@ -10392,26 +10623,100 @@ export class Viewer3D {
|
|
|
10392
10623
|
}
|
|
10393
10624
|
trajectoryGroup.add(this.createTrajectoryObject(target, points));
|
|
10394
10625
|
}
|
|
10395
|
-
this.updateTrajectoryCurrentMarkers();
|
|
10396
10626
|
this.updateTrajectoryGroupVisibility();
|
|
10627
|
+
this.updateTrajectoryCurrentMarkers();
|
|
10397
10628
|
this.requestRender();
|
|
10398
10629
|
}
|
|
10630
|
+
applyTrajectorySamplePose(animation, time, duration) {
|
|
10631
|
+
const joints = this.getRobotJoints();
|
|
10632
|
+
for (const { target, value } of sampleRobotMotionJointValuesAtTime(animation, time, duration)) {
|
|
10633
|
+
const joint = joints[target];
|
|
10634
|
+
if (joint)
|
|
10635
|
+
this.applyDesiredProjectionJointValue(joint, value);
|
|
10636
|
+
}
|
|
10637
|
+
}
|
|
10638
|
+
applyTrajectoryTemporalSamplePose(sample, rootAlignment) {
|
|
10639
|
+
const viewer = sample.viewer;
|
|
10640
|
+
const robot = this.robot;
|
|
10641
|
+
if (!viewer || !robot) {
|
|
10642
|
+
return rootAlignment;
|
|
10643
|
+
}
|
|
10644
|
+
const joints = this.getRobotJoints();
|
|
10645
|
+
for (const [target, value] of Object.entries(viewer.joints ?? {})) {
|
|
10646
|
+
const joint = joints[target];
|
|
10647
|
+
if (joint && Number.isFinite(value)) {
|
|
10648
|
+
this.applyDesiredProjectionJointValue(joint, value);
|
|
10649
|
+
}
|
|
10650
|
+
}
|
|
10651
|
+
const bodyTransforms = (viewer.physics?.bodyTransforms ?? []).map((transform) => this.deserializeRuntimeHistoryTransform(transform, false));
|
|
10652
|
+
const rootTransform = bodyTransforms.find((transform) => transform.metadata?.visualRoot === true) ??
|
|
10653
|
+
bodyTransforms.find((transform) => transform.bodyName === this.physicsVisualRootBodyName);
|
|
10654
|
+
let nextRootAlignment = rootAlignment;
|
|
10655
|
+
if (rootTransform) {
|
|
10656
|
+
const physicsRootWorldMatrix = this.createPhysicsBodyViewerWorldMatrix(rootTransform);
|
|
10657
|
+
nextRootAlignment ??= this.resolvePhysicsVisualRootToRobotMatrix(physicsRootWorldMatrix, bodyTransforms);
|
|
10658
|
+
const nextRobotLocalMatrix = physicsRootWorldMatrix.clone().multiply(nextRootAlignment);
|
|
10659
|
+
const parent = robot.parent;
|
|
10660
|
+
if (parent && parent !== this.simulatedRobotPresentationRoot) {
|
|
10661
|
+
parent.updateMatrixWorld(true);
|
|
10662
|
+
nextRobotLocalMatrix.premultiply(parent.matrixWorld.clone().invert());
|
|
10663
|
+
}
|
|
10664
|
+
robot.matrix.copy(nextRobotLocalMatrix);
|
|
10665
|
+
robot.matrix.decompose(robot.position, robot.quaternion, robot.scale);
|
|
10666
|
+
}
|
|
10667
|
+
robot.updateMatrixWorld(true);
|
|
10668
|
+
return nextRootAlignment;
|
|
10669
|
+
}
|
|
10670
|
+
restoreTrajectorySamplePose(pose) {
|
|
10671
|
+
const joints = this.getRobotJoints();
|
|
10672
|
+
for (const [target, value] of Object.entries(pose)) {
|
|
10673
|
+
const joint = joints[target];
|
|
10674
|
+
if (joint)
|
|
10675
|
+
this.applyDesiredProjectionJointValue(joint, value);
|
|
10676
|
+
}
|
|
10677
|
+
this.robot?.updateMatrixWorld(true);
|
|
10678
|
+
}
|
|
10399
10679
|
getTrajectorySignature(animation, targets, duration) {
|
|
10400
10680
|
return JSON.stringify({
|
|
10401
10681
|
duration: Number(duration.toFixed(3)),
|
|
10682
|
+
projectionRevision: this.trajectoryProjectionRevision,
|
|
10683
|
+
playbackProjection: this.getTrajectoryPlaybackProjectionSignature(animation),
|
|
10402
10684
|
handsOnly: this.options.showHandTrajectoriesOnly,
|
|
10403
10685
|
targets: targets.map((target) => target.id),
|
|
10686
|
+
temporalProjection: this.getTrajectoryTemporalProjectionSignature(animation, duration),
|
|
10404
10687
|
tracks: animation.motion.tracks.map((track) => ({
|
|
10405
10688
|
target: track.target,
|
|
10406
10689
|
property: track.property,
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
firstValue: track.keys[0]?.value ?? null,
|
|
10411
|
-
lastValue: track.keys[track.keys.length - 1]?.value ?? null
|
|
10690
|
+
interpolation: track.interpolation ?? null,
|
|
10691
|
+
unitKind: track.unitKind ?? null,
|
|
10692
|
+
keys: track.keys.map((key) => [key.time, key.value])
|
|
10412
10693
|
}))
|
|
10413
10694
|
});
|
|
10414
10695
|
}
|
|
10696
|
+
getTrajectoryPlaybackProjectionSignature(animation) {
|
|
10697
|
+
const playback = this.playbackState;
|
|
10698
|
+
if (playback?.animation !== animation) {
|
|
10699
|
+
return null;
|
|
10700
|
+
}
|
|
10701
|
+
return {
|
|
10702
|
+
mutedTargets: [...playback.mutedTrackTargetSet].sort(),
|
|
10703
|
+
startTransitionMinimumDurationSeconds: playback.startTransitionMinimumDurationSeconds,
|
|
10704
|
+
entryPose: this.createPoseSignature(playback.entryPose),
|
|
10705
|
+
endTransitionMinimumDurationSeconds: playback.endTransitionMinimumDurationSeconds,
|
|
10706
|
+
exitPose: this.createPoseSignature(playback.exitPose)
|
|
10707
|
+
};
|
|
10708
|
+
}
|
|
10709
|
+
getTrajectoryTemporalProjectionSignature(animation, duration) {
|
|
10710
|
+
const playback = this.playbackState;
|
|
10711
|
+
const resolver = playback?.animation === animation ? playback.resolveTemporalProjectionSample : null;
|
|
10712
|
+
if (!resolver) {
|
|
10713
|
+
return null;
|
|
10714
|
+
}
|
|
10715
|
+
return [0, duration * 0.5, duration].map((time) => {
|
|
10716
|
+
const sample = resolver(time);
|
|
10717
|
+
return sample ? [sample.id, Number(sample.timeSeconds.toFixed(4))] : null;
|
|
10718
|
+
});
|
|
10719
|
+
}
|
|
10415
10720
|
getTrajectoryPreviewTargets(animation) {
|
|
10416
10721
|
if (this.options.showHandTrajectoriesOnly) {
|
|
10417
10722
|
return (this.options.handTipDefinitions ?? [])
|
|
@@ -10502,9 +10807,12 @@ export class Viewer3D {
|
|
|
10502
10807
|
if (!target || !currentMarker) {
|
|
10503
10808
|
continue;
|
|
10504
10809
|
}
|
|
10505
|
-
currentMarker.position.copy(this.
|
|
10810
|
+
currentMarker.position.copy(this.getTrajectoryPointInProjectionFrame(target));
|
|
10506
10811
|
}
|
|
10507
10812
|
}
|
|
10813
|
+
getTrajectoryPointInProjectionFrame(target) {
|
|
10814
|
+
return projectViewerTrajectoryPointToFrame(this.getTrajectoryPoint(target), this.simulatedRobotPresentationRoot);
|
|
10815
|
+
}
|
|
10508
10816
|
getTrajectoryPoint(target) {
|
|
10509
10817
|
const handTip = (this.options.handTipDefinitions ?? []).find((definition) => definition.id === target);
|
|
10510
10818
|
if (handTip) {
|
|
@@ -10583,10 +10891,10 @@ export class Viewer3D {
|
|
|
10583
10891
|
this.trajectoryAnchors.set(target, anchor);
|
|
10584
10892
|
return anchor;
|
|
10585
10893
|
}
|
|
10894
|
+
joint.updateMatrixWorld?.(true);
|
|
10586
10895
|
const jointInverse = joint.matrixWorld.clone().invert();
|
|
10587
10896
|
const localBounds = new Box3();
|
|
10588
10897
|
let hasBounds = false;
|
|
10589
|
-
joint.updateMatrixWorld?.(true);
|
|
10590
10898
|
joint.traverse((node) => {
|
|
10591
10899
|
if (node !== joint && node.isURDFJoint) {
|
|
10592
10900
|
return;
|
|
@@ -10880,6 +11188,11 @@ export class Viewer3D {
|
|
|
10880
11188
|
function formatNavigationDistance(value) {
|
|
10881
11189
|
return `${value >= 0 ? '+' : ''}${value.toFixed(2)}m`;
|
|
10882
11190
|
}
|
|
11191
|
+
/** Projects a world-space trajectory sample into the robot presentation frame. */
|
|
11192
|
+
export function projectViewerTrajectoryPointToFrame(point, frame) {
|
|
11193
|
+
frame.updateWorldMatrix(true, false);
|
|
11194
|
+
return frame.worldToLocal(new Vector3(point.x, point.y, point.z));
|
|
11195
|
+
}
|
|
10883
11196
|
function formatNavigationAngle(value) {
|
|
10884
11197
|
const degrees = MathUtils.radToDeg(normalizeNavigationRadians(value));
|
|
10885
11198
|
return `${degrees >= 0 ? '+' : ''}${degrees.toFixed(0)}°`;
|