@konitif/viewer-3d 0.285.0 → 0.285.2

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.
@@ -18,7 +18,7 @@ import { SMAAPass } from 'three/examples/jsm/postprocessing/SMAAPass.js';
18
18
  import { HorizontalTiltShiftShader } from 'three/examples/jsm/shaders/HorizontalTiltShiftShader.js';
19
19
  import { VerticalTiltShiftShader } from 'three/examples/jsm/shaders/VerticalTiltShiftShader.js';
20
20
  import { getRobotMotionAnimationDuration, interpolateRobotMotionTrackValueAtTime, toRobotMotionJointValue, resolveRobotMotionPlaybackDuration } from './robotViewerMotion.js';
21
- import { createViewerProjectileLaunchSolution, createViewerProjectileTrajectoryPoints, resolveViewerProjectileChargedLaunchProfile, resolveViewerProjectileImpulse, resolveViewerProjectileLauncherWorldScale, resolveViewerSlingshotReleasePull, stepViewerProjectileAgainstGround } from './ProjectileInteraction.js';
21
+ import { createViewerProjectileLaunchSolution, VIEWER3D_PROJECTILE_PARTICLE_LIMIT, createViewerProjectileTrajectoryPoints, resolveViewerProjectileChargedLaunchProfile, resolveViewerProjectileImpulse, resolveViewerProjectileLauncherWorldScale, resolveViewerSlingshotReleasePull, stepViewerProjectileAgainstGround } from './ProjectileInteraction.js';
22
22
  import { createInteractionKeyboardSignal, DEFAULT_VIEWER_INPUT_BINDINGS, VIEWER_PROJECTILE_CHARGE_ACTION_ID, VIEWER_PROJECTILE_LAUNCH_ACTION_ID } from './ViewerInputBindings.js';
23
23
  import { ViewerProjectileParticleRenderer } from './ViewerProjectileParticleRenderer.js';
24
24
  import { ROBOT_COMPARISON_VISIBILITY_LAYOUT_SHARE, resolveRobotComparisonVisibilityTransitionPhases } from './RobotComparisonVisibilityTransition.js';
@@ -64,7 +64,7 @@ const ROBOT_VIEWER_SCRUB_PHYSICS_SYNC_MINIMUM_INTERVAL_MS = 1000 / 30;
64
64
  const ROBOT_VIEWER_PROJECTILE_MASS_KG = 0.18;
65
65
  const ROBOT_VIEWER_PROJECTILE_RADIUS_M = 0.03;
66
66
  const ROBOT_VIEWER_PROJECTILE_SETTLE_MS = 3_000;
67
- export const VIEWER3D_PROJECTILE_PARTICLE_LIMIT = 24;
67
+ export { VIEWER3D_PROJECTILE_PARTICLE_LIMIT } from './ProjectileInteraction.js';
68
68
  const ROBOT_VIEWER_PROJECTILE_MAX_LIFETIME_MS = 12_000;
69
69
  const ROBOT_VIEWER_PROJECTILE_SETTLED_HOLD_MS = 1_300;
70
70
  const ROBOT_VIEWER_PROJECTILE_FADE_MS = 900;
@@ -192,6 +192,7 @@ const VIEWER3D_DEFAULT_CAMERA_POSITION = { x: 1.4, y: 0.9, z: 1.4 };
192
192
  const VIEWER3D_DEFAULT_CAMERA_TARGET = { x: 0, y: 0.32, z: 0 };
193
193
  const EMPTY_MUTED_TRACK_TARGET_SET = new Set();
194
194
  export class Viewer3D {
195
+ displayedRecordedPhysicsObservation = null;
195
196
  container;
196
197
  renderer;
197
198
  scene;
@@ -1275,6 +1276,79 @@ export class Viewer3D {
1275
1276
  getPhysicsSnapshot() {
1276
1277
  return this.physicsService.snapshot();
1277
1278
  }
1279
+ /** Project an already assembled URDF root; presentation offsets are excluded. */
1280
+ readDisplayedRobotRootTransform(target, frame) {
1281
+ const robot = target === 'observed' ? this.observedRobotGhost : this.robot;
1282
+ if (!robot)
1283
+ return null;
1284
+ // A hidden ghost still supplies its grounded projection to external viewers.
1285
+ if (target === 'observed')
1286
+ this.applyObservedRobotGhostGrounding();
1287
+ robot.updateMatrix();
1288
+ const reference = { bodyName: frame.bodyName, position: { x: 0, y: 0, z: 0 },
1289
+ rotation: { x: 0, y: 0, z: 0, w: 1 },
1290
+ metadata: { visualRoot: true, coordinateFrame: frame.coordinateFrame, sceneYawRadians: frame.sceneYawRadians } };
1291
+ const canonical = this.createPhysicsCoordinateFrameMatrix(reference).invert().multiply(robot.matrix);
1292
+ const position = new Vector3(), rotation = new Quaternion(), scale = new Vector3();
1293
+ canonical.decompose(position, rotation, scale);
1294
+ return { ...reference, position: { x: position.x, y: position.y, z: position.z },
1295
+ rotation: { x: rotation.x, y: rotation.y, z: rotation.z, w: rotation.w } };
1296
+ }
1297
+ readDisplayedRobotObservation(timeSeconds) {
1298
+ if (!this.robot)
1299
+ return null;
1300
+ const physical = this.readPhysicsObservation();
1301
+ const jointAngles = this.captureRuntimeHistoryVisualJointStates();
1302
+ const bodies = this.projectedPhysicsBodyTransforms.length ? this.projectedPhysicsBodyTransforms : physical?.bodyTransforms.length ? physical.bodyTransforms : this.physicsService.getBodyTransforms();
1303
+ const root = bodies.find(body => body.metadata?.visualRoot === true) ?? bodies.find(body => body.bodyName === this.physicsVisualRootBodyName);
1304
+ let rootTransform;
1305
+ const alignment = root ? this.physicsVisualRootToRobotMatrix ?? this.resolvePhysicsVisualRootToRobotMatrix(this.createPhysicsBodyViewerWorldMatrix(root), bodies) : null;
1306
+ if (root && alignment) {
1307
+ this.robot.updateMatrix();
1308
+ const robotMatrix = this.robot.matrix.clone();
1309
+ if (this.robot.parent && this.robot.parent !== this.simulatedRobotPresentationRoot) {
1310
+ this.robot.parent.updateMatrixWorld(true);
1311
+ robotMatrix.premultiply(this.robot.parent.matrixWorld);
1312
+ }
1313
+ const canonicalMatrix = this.createPhysicsCoordinateFrameMatrix(root).invert()
1314
+ .multiply(robotMatrix).multiply(alignment.clone().invert());
1315
+ const position = new Vector3(), rotation = new Quaternion(), scale = new Vector3();
1316
+ canonicalMatrix.decompose(position, rotation, scale);
1317
+ rootTransform = { ...structuredClone(root), position: { x: position.x, y: position.y, z: position.z }, rotation: { x: rotation.x, y: rotation.y, z: rotation.z, w: rotation.w } };
1318
+ }
1319
+ const time = Number.isFinite(timeSeconds) ? timeSeconds : physical?.simulatedTimeSeconds ?? 0;
1320
+ return {
1321
+ sampleId: `displayed:${time}:${JSON.stringify(jointAngles)}:${JSON.stringify(rootTransform)}`,
1322
+ observedAtMs: performance.now(), simulatedTimeSeconds: time,
1323
+ jointAngles, rootTransform, bodyTransforms: structuredClone(bodies), centerOfMass: this.projectedPhysicsCenterOfMass ?? physical?.centerOfMass ?? this.physicsService.getCenterOfMass(), navigation: null,
1324
+ projectionStage: this.displayedRecordedPhysicsObservation ? 'simulated' : 'authored'
1325
+ };
1326
+ }
1327
+ readPhysicsObservation() {
1328
+ if (this.displayedRecordedPhysicsObservation)
1329
+ return structuredClone(this.displayedRecordedPhysicsObservation);
1330
+ if (this.playbackState?.animation && this.robot) {
1331
+ const jointAngles = this.captureRuntimeHistoryVisualJointStates();
1332
+ const time = this.playbackState.currentTime;
1333
+ return {
1334
+ sampleId: `displayed:${this.playbackState.sourceId ?? 'animation'}:${time}:${JSON.stringify(jointAngles)}`,
1335
+ observedAtMs: performance.now(), simulatedTimeSeconds: time,
1336
+ projectionStage: 'authored', jointAngles, bodyTransforms: [], centerOfMass: null, navigation: null
1337
+ };
1338
+ }
1339
+ const snapshot = this.physicsService.snapshot();
1340
+ const bodies = this.physicsService.getBodyTransforms();
1341
+ if (!snapshot.loadedSourceId || !bodies.length)
1342
+ return null;
1343
+ const metadata = snapshot.backendStatus?.metadata;
1344
+ const time = typeof metadata?.simulatedTime === 'number' ? metadata.simulatedTime : 0;
1345
+ return {
1346
+ sampleId: `${snapshot.engine}:${snapshot.loadedSourceId}:${metadata?.stepCount ?? 0}:${metadata?.stateRevision ?? 0}`,
1347
+ observedAtMs: performance.now(), simulatedTimeSeconds: time,
1348
+ jointAngles: this.capturePhysicsVisualJointAngles(),
1349
+ bodyTransforms: structuredClone(bodies), centerOfMass: this.physicsService.getCenterOfMass(), navigation: null
1350
+ };
1351
+ }
1278
1352
  readNavigationPhysicsObservation() {
1279
1353
  const snapshot = this.physicsService.snapshot();
1280
1354
  if (!snapshot.enabled ||
@@ -1502,6 +1576,7 @@ export class Viewer3D {
1502
1576
  return true;
1503
1577
  }
1504
1578
  resumeRuntimeHistoryLiveProjection() {
1579
+ this.displayedRecordedPhysicsObservation = null;
1505
1580
  if (!this.runtimeHistoryReplayActive) {
1506
1581
  return;
1507
1582
  }
@@ -2377,6 +2452,9 @@ export class Viewer3D {
2377
2452
  this.deferredBoundaryPreviewRuntimeHistoryProjection = null;
2378
2453
  if (!transition) {
2379
2454
  this.boundaryPosePreviewTransitionState = null;
2455
+ this.groundSimulatedRobotOnSupportFloor(this.robot);
2456
+ this.syncPhysicsKinematicPoseFromRobotPose({ holdFrames: 0, clearDynamics: true, resetCadence: false });
2457
+ this.updateViewerDebugLayers();
2380
2458
  this.requestRender();
2381
2459
  return true;
2382
2460
  }
@@ -2436,6 +2514,7 @@ export class Viewer3D {
2436
2514
  return this.createMutedTrackTargetSet(mutedTrackTargets);
2437
2515
  }
2438
2516
  applyRobotPose(animation, time, mutedTrackTargets = [], durationOverride = null) {
2517
+ this.displayedRecordedPhysicsObservation = null;
2439
2518
  if (!this.robot) {
2440
2519
  return false;
2441
2520
  }
@@ -2886,6 +2965,10 @@ export class Viewer3D {
2886
2965
  poseChanged = this.applyDesiredProjectionJointValue(joint, value) || poseChanged;
2887
2966
  }
2888
2967
  if (poseChanged) {
2968
+ // A catalogue pose owns joint angles, not the previous incarnation's root height.
2969
+ // Ground only this authored preview; recorded physical samples retain their root.
2970
+ if (this.robot)
2971
+ this.groundSimulatedRobotOnSupportFloor(this.robot);
2889
2972
  this.robot?.updateMatrixWorld(true);
2890
2973
  this.updateViewerDebugLayers();
2891
2974
  this.syncPhysicsKinematicPoseFromRobotPose({
@@ -4101,6 +4184,7 @@ export class Viewer3D {
4101
4184
  }
4102
4185
  const receivedPhysicsSample = sampleId !== this.lastTemporalProjectionSampleId;
4103
4186
  if (receivedPhysicsSample) {
4187
+ this.displayedRecordedPhysicsObservation = null;
4104
4188
  this.markFrameSignal('physics-observation');
4105
4189
  this.lastTemporalProjectionSampleId = sampleId;
4106
4190
  this.temporalProjection.ingest({
@@ -4114,6 +4198,7 @@ export class Viewer3D {
4114
4198
  });
4115
4199
  if (this.options.physicsObservationEnabled) {
4116
4200
  this.options.onPhysicsObservation({
4201
+ jointAngles: this.capturePhysicsVisualJointAngles(),
4117
4202
  sampleId,
4118
4203
  observedAtMs: now,
4119
4204
  simulatedTimeSeconds: simulatedTime,
@@ -4394,6 +4479,22 @@ export class Viewer3D {
4394
4479
  // never the last live/bake observation left in these projection fields.
4395
4480
  this.projectedPhysicsBodyTransforms = bodyTransforms;
4396
4481
  this.projectedPhysicsCenterOfMass = centerOfMass;
4482
+ if (viewer.physics?.enabled && bodyTransforms.length > 0) {
4483
+ this.displayedRecordedPhysicsObservation = {
4484
+ projectionStage: 'simulated',
4485
+ sampleId: `recorded:${sample.id}:${sample.timeSeconds}`,
4486
+ observedAtMs: performance.now(), simulatedTimeSeconds: sample.timeSeconds,
4487
+ jointAngles: { ...(viewer.joints ?? {}) },
4488
+ bodyTransforms: structuredClone(bodyTransforms),
4489
+ centerOfMass: viewer.physics.centerOfMass ? { ...viewer.physics.centerOfMass } : null,
4490
+ navigation: null
4491
+ };
4492
+ if (this.options.physicsObservationEnabled)
4493
+ this.options.onPhysicsObservation(structuredClone(this.displayedRecordedPhysicsObservation));
4494
+ }
4495
+ else {
4496
+ this.displayedRecordedPhysicsObservation = null;
4497
+ }
4397
4498
  const hasVisualJointStates = viewer.joints && Object.keys(viewer.joints).length > 0;
4398
4499
  const visualJointPoseChanged = this.applyRuntimeHistoryVisualJointStates(viewer.joints ?? null);
4399
4500
  poseChanged = visualJointPoseChanged;
@@ -9146,28 +9247,8 @@ export class Viewer3D {
9146
9247
  resolveEyeRingCalibration() {
9147
9248
  const defaults = this.options.ledRingDefaults?.eyes ?? ROBOT_VIEWER_EMPTY_LED_RING_CALIBRATION;
9148
9249
  const calibration = this.options.eyeRingCalibration ?? {};
9149
- const legacyCalibration = calibration;
9150
9250
  return {
9151
- layout: this.resolveLedLayout(calibration.layout, defaults.layout),
9152
- forwardOffset: this.resolveFiniteCalibrationNumber(calibration.forwardOffset, defaults.forwardOffset),
9153
- verticalOffset: this.resolveFiniteCalibrationNumber(calibration.verticalOffset, defaults.verticalOffset),
9154
- lateralOffset: this.resolveFiniteCalibrationNumber(calibration.lateralOffset, defaults.lateralOffset ?? 0),
9155
- separation: this.resolveFiniteCalibrationNumber(calibration.separation, defaults.separation),
9156
- radius: this.resolveFiniteCalibrationNumber(calibration.radius, defaults.radius),
9157
- stripLength: this.resolveFiniteCalibrationNumber(calibration.stripLength, defaults.stripLength ?? defaults.radius * 2),
9158
- ledCount: this.resolveFiniteCalibrationNumber(calibration.ledCount, defaults.ledCount),
9159
- ledSize: this.resolveFiniteCalibrationNumber(calibration.ledSize, defaults.ledSize),
9160
- rotationX: this.resolveFiniteCalibrationNumber(calibration.rotationX, defaults.rotationX),
9161
- rotationY: this.resolveFiniteCalibrationNumber(calibration.rotationY ?? legacyCalibration.tiltY, defaults.rotationY),
9162
- rotationZ: this.resolveFiniteCalibrationNumber(calibration.rotationZ ?? legacyCalibration.tiltX, defaults.rotationZ),
9163
- isVisible: typeof calibration.isVisible === 'boolean'
9164
- ? calibration.isVisible
9165
- : defaults.isVisible,
9166
- mireVisible: typeof calibration.mireVisible === 'boolean'
9167
- ? calibration.mireVisible
9168
- : defaults.mireVisible,
9169
- variantCount: this.resolveFiniteCalibrationNumber(calibration.variantCount, defaults.variantCount),
9170
- variantStep: this.resolveFiniteCalibrationNumber(calibration.variantStep, defaults.variantStep),
9251
+ ...resolveViewerLedRingCalibration(calibration, defaults),
9171
9252
  zones: this.resolveLedRingCalibrationZones(calibration.zones)
9172
9253
  };
9173
9254
  }
@@ -9205,34 +9286,9 @@ export class Viewer3D {
9205
9286
  resolveFiniteCalibrationNumber(value, fallback) {
9206
9287
  return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
9207
9288
  }
9208
- resolveLedLayout(value, fallback = 'circle') {
9209
- if (value === 'strip' || value === 'circle')
9210
- return value;
9211
- return fallback === 'strip' ? 'strip' : 'circle';
9212
- }
9213
9289
  resolveLedRingZoneCalibration(calibration, zoneId) {
9214
- const base = zoneId === 'eyes'
9215
- ? calibration
9216
- : (this.options.ledRingDefaults?.[zoneId] ?? ROBOT_VIEWER_EMPTY_LED_RING_CALIBRATION);
9217
- const override = calibration.zones?.[zoneId] ?? {};
9218
- return {
9219
- layout: this.resolveLedLayout(override.layout, base.layout),
9220
- forwardOffset: this.resolveFiniteCalibrationNumber(override.forwardOffset, base.forwardOffset),
9221
- verticalOffset: this.resolveFiniteCalibrationNumber(override.verticalOffset, base.verticalOffset),
9222
- lateralOffset: this.resolveFiniteCalibrationNumber(override.lateralOffset, base.lateralOffset ?? 0),
9223
- separation: this.resolveFiniteCalibrationNumber(override.separation, base.separation),
9224
- radius: this.resolveFiniteCalibrationNumber(override.radius, base.radius),
9225
- stripLength: this.resolveFiniteCalibrationNumber(override.stripLength, base.stripLength ?? base.radius * 2),
9226
- ledCount: this.resolveFiniteCalibrationNumber(override.ledCount, base.ledCount),
9227
- ledSize: this.resolveFiniteCalibrationNumber(override.ledSize, base.ledSize),
9228
- rotationX: this.resolveFiniteCalibrationNumber(override.rotationX, base.rotationX),
9229
- rotationY: this.resolveFiniteCalibrationNumber(override.rotationY, base.rotationY),
9230
- rotationZ: this.resolveFiniteCalibrationNumber(override.rotationZ, base.rotationZ),
9231
- isVisible: typeof override.isVisible === 'boolean' ? override.isVisible : base.isVisible,
9232
- mireVisible: typeof override.mireVisible === 'boolean' ? override.mireVisible : base.mireVisible,
9233
- variantCount: this.resolveFiniteCalibrationNumber(override.variantCount, base.variantCount),
9234
- variantStep: this.resolveFiniteCalibrationNumber(override.variantStep, base.variantStep)
9235
- };
9290
+ const base = zoneId === 'eyes' ? calibration : (this.options.ledRingDefaults?.[zoneId] ?? ROBOT_VIEWER_EMPTY_LED_RING_CALIBRATION);
9291
+ return resolveViewerLedRingCalibration(calibration.zones?.[zoneId], base);
9236
9292
  }
9237
9293
  resolveEyeRingDebugDimensions(calibration) {
9238
9294
  const variantCount = Math.max(1, Math.min(7, Math.round(calibration.variantCount)));
@@ -10652,4 +10708,5 @@ function sampleNavigationPath(points, progress) {
10652
10708
  }
10653
10709
  return points[points.length - 1]?.clone() ?? new Vector3();
10654
10710
  }
10711
+ import { resolveViewerLedRingCalibration } from '../ledRingCalibration.js';
10655
10712
  //# sourceMappingURL=Viewer3DRenderer.js.map