@konitif/viewer-3d 0.284.2 → 0.285.1

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;
@@ -1201,7 +1202,7 @@ export class Viewer3D {
1201
1202
  this.playbackTransitionState = null;
1202
1203
  }
1203
1204
  }
1204
- async loadPhysicsRobot(source) {
1205
+ async loadPhysicsSubject(source) {
1205
1206
  if (this.destroyed)
1206
1207
  return;
1207
1208
  const visualAlignment = resolvePhysicsVisualAlignmentProfile(source.metadata?.visualAlignment);
@@ -1219,7 +1220,7 @@ export class Viewer3D {
1219
1220
  this.physicsResetBaselineCaptureFrames = 0;
1220
1221
  this.physicsAuthoredJointTargets.clear();
1221
1222
  this.resetPhysicsPostureCompensation();
1222
- await this.physicsService.loadRobot(source);
1223
+ await this.physicsService.loadSubject(source);
1223
1224
  if (this.destroyed)
1224
1225
  return;
1225
1226
  this.resetTemporalProjection();
@@ -1275,6 +1276,60 @@ export class Viewer3D {
1275
1276
  getPhysicsSnapshot() {
1276
1277
  return this.physicsService.snapshot();
1277
1278
  }
1279
+ readDisplayedRobotObservation(timeSeconds) {
1280
+ if (!this.robot)
1281
+ return null;
1282
+ const physical = this.readPhysicsObservation();
1283
+ const jointAngles = this.captureRuntimeHistoryVisualJointStates();
1284
+ const bodies = physical?.bodyTransforms ?? this.physicsService.getBodyTransforms();
1285
+ const root = bodies.find(body => body.metadata?.visualRoot === true) ?? bodies.find(body => body.bodyName === this.physicsVisualRootBodyName);
1286
+ let bodyTransforms = [];
1287
+ if (root && this.physicsVisualRootToRobotMatrix) {
1288
+ this.robot.updateMatrix();
1289
+ const robotMatrix = this.robot.matrix.clone();
1290
+ if (this.robot.parent && this.robot.parent !== this.simulatedRobotPresentationRoot) {
1291
+ this.robot.parent.updateMatrixWorld(true);
1292
+ robotMatrix.premultiply(this.robot.parent.matrixWorld);
1293
+ }
1294
+ const canonicalMatrix = this.createPhysicsCoordinateFrameMatrix(root).invert()
1295
+ .multiply(robotMatrix).multiply(this.physicsVisualRootToRobotMatrix.clone().invert());
1296
+ const position = new Vector3(), rotation = new Quaternion(), scale = new Vector3();
1297
+ canonicalMatrix.decompose(position, rotation, scale);
1298
+ bodyTransforms = [{ ...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 } }];
1299
+ }
1300
+ const time = Number.isFinite(timeSeconds) ? timeSeconds : physical?.simulatedTimeSeconds ?? 0;
1301
+ return {
1302
+ sampleId: `displayed:${time}:${JSON.stringify(jointAngles)}:${JSON.stringify(bodyTransforms)}`,
1303
+ observedAtMs: performance.now(), simulatedTimeSeconds: time,
1304
+ jointAngles, bodyTransforms, centerOfMass: physical?.centerOfMass ?? null, navigation: null,
1305
+ projectionStage: this.displayedRecordedPhysicsObservation ? 'simulated' : 'authored'
1306
+ };
1307
+ }
1308
+ readPhysicsObservation() {
1309
+ if (this.displayedRecordedPhysicsObservation)
1310
+ return structuredClone(this.displayedRecordedPhysicsObservation);
1311
+ if (this.playbackState?.animation && this.robot) {
1312
+ const jointAngles = this.captureRuntimeHistoryVisualJointStates();
1313
+ const time = this.playbackState.currentTime;
1314
+ return {
1315
+ sampleId: `displayed:${this.playbackState.sourceId ?? 'animation'}:${time}:${JSON.stringify(jointAngles)}`,
1316
+ observedAtMs: performance.now(), simulatedTimeSeconds: time,
1317
+ projectionStage: 'authored', jointAngles, bodyTransforms: [], centerOfMass: null, navigation: null
1318
+ };
1319
+ }
1320
+ const snapshot = this.physicsService.snapshot();
1321
+ const bodies = this.physicsService.getBodyTransforms();
1322
+ if (!snapshot.loadedSourceId || !bodies.length)
1323
+ return null;
1324
+ const metadata = snapshot.backendStatus?.metadata;
1325
+ const time = typeof metadata?.simulatedTime === 'number' ? metadata.simulatedTime : 0;
1326
+ return {
1327
+ sampleId: `${snapshot.engine}:${snapshot.loadedSourceId}:${metadata?.stepCount ?? 0}:${metadata?.stateRevision ?? 0}`,
1328
+ observedAtMs: performance.now(), simulatedTimeSeconds: time,
1329
+ jointAngles: this.capturePhysicsVisualJointAngles(),
1330
+ bodyTransforms: structuredClone(bodies), centerOfMass: this.physicsService.getCenterOfMass(), navigation: null
1331
+ };
1332
+ }
1278
1333
  readNavigationPhysicsObservation() {
1279
1334
  const snapshot = this.physicsService.snapshot();
1280
1335
  if (!snapshot.enabled ||
@@ -1502,6 +1557,7 @@ export class Viewer3D {
1502
1557
  return true;
1503
1558
  }
1504
1559
  resumeRuntimeHistoryLiveProjection() {
1560
+ this.displayedRecordedPhysicsObservation = null;
1505
1561
  if (!this.runtimeHistoryReplayActive) {
1506
1562
  return;
1507
1563
  }
@@ -2436,6 +2492,7 @@ export class Viewer3D {
2436
2492
  return this.createMutedTrackTargetSet(mutedTrackTargets);
2437
2493
  }
2438
2494
  applyRobotPose(animation, time, mutedTrackTargets = [], durationOverride = null) {
2495
+ this.displayedRecordedPhysicsObservation = null;
2439
2496
  if (!this.robot) {
2440
2497
  return false;
2441
2498
  }
@@ -4101,6 +4158,7 @@ export class Viewer3D {
4101
4158
  }
4102
4159
  const receivedPhysicsSample = sampleId !== this.lastTemporalProjectionSampleId;
4103
4160
  if (receivedPhysicsSample) {
4161
+ this.displayedRecordedPhysicsObservation = null;
4104
4162
  this.markFrameSignal('physics-observation');
4105
4163
  this.lastTemporalProjectionSampleId = sampleId;
4106
4164
  this.temporalProjection.ingest({
@@ -4114,6 +4172,7 @@ export class Viewer3D {
4114
4172
  });
4115
4173
  if (this.options.physicsObservationEnabled) {
4116
4174
  this.options.onPhysicsObservation({
4175
+ jointAngles: this.capturePhysicsVisualJointAngles(),
4117
4176
  sampleId,
4118
4177
  observedAtMs: now,
4119
4178
  simulatedTimeSeconds: simulatedTime,
@@ -4394,6 +4453,22 @@ export class Viewer3D {
4394
4453
  // never the last live/bake observation left in these projection fields.
4395
4454
  this.projectedPhysicsBodyTransforms = bodyTransforms;
4396
4455
  this.projectedPhysicsCenterOfMass = centerOfMass;
4456
+ if (viewer.physics?.enabled && bodyTransforms.length > 0) {
4457
+ this.displayedRecordedPhysicsObservation = {
4458
+ projectionStage: 'simulated',
4459
+ sampleId: `recorded:${sample.id}:${sample.timeSeconds}`,
4460
+ observedAtMs: performance.now(), simulatedTimeSeconds: sample.timeSeconds,
4461
+ jointAngles: { ...(viewer.joints ?? {}) },
4462
+ bodyTransforms: structuredClone(bodyTransforms),
4463
+ centerOfMass: viewer.physics.centerOfMass ? { ...viewer.physics.centerOfMass } : null,
4464
+ navigation: null
4465
+ };
4466
+ if (this.options.physicsObservationEnabled)
4467
+ this.options.onPhysicsObservation(structuredClone(this.displayedRecordedPhysicsObservation));
4468
+ }
4469
+ else {
4470
+ this.displayedRecordedPhysicsObservation = null;
4471
+ }
4397
4472
  const hasVisualJointStates = viewer.joints && Object.keys(viewer.joints).length > 0;
4398
4473
  const visualJointPoseChanged = this.applyRuntimeHistoryVisualJointStates(viewer.joints ?? null);
4399
4474
  poseChanged = visualJointPoseChanged;
@@ -5264,7 +5339,7 @@ export class Viewer3D {
5264
5339
  }
5265
5340
  resolveRobotSupportGeometryBounds(robot, candidates) {
5266
5341
  // FSR frames are useful semantic anchors but are intentionally empty in
5267
- // the NAO model. Resolve the first candidate that owns rendered geometry
5342
+ // an articulated model. Resolve the first candidate that owns rendered geometry
5268
5343
  // instead of accepting an empty frame and falling back to whole-body
5269
5344
  // bounds, which left the soles visibly above the support floor.
5270
5345
  for (const candidate of candidates) {
@@ -9146,28 +9221,8 @@ export class Viewer3D {
9146
9221
  resolveEyeRingCalibration() {
9147
9222
  const defaults = this.options.ledRingDefaults?.eyes ?? ROBOT_VIEWER_EMPTY_LED_RING_CALIBRATION;
9148
9223
  const calibration = this.options.eyeRingCalibration ?? {};
9149
- const legacyCalibration = calibration;
9150
9224
  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),
9225
+ ...resolveViewerLedRingCalibration(calibration, defaults),
9171
9226
  zones: this.resolveLedRingCalibrationZones(calibration.zones)
9172
9227
  };
9173
9228
  }
@@ -9205,34 +9260,9 @@ export class Viewer3D {
9205
9260
  resolveFiniteCalibrationNumber(value, fallback) {
9206
9261
  return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
9207
9262
  }
9208
- resolveLedLayout(value, fallback = 'circle') {
9209
- if (value === 'strip' || value === 'circle')
9210
- return value;
9211
- return fallback === 'strip' ? 'strip' : 'circle';
9212
- }
9213
9263
  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
- };
9264
+ const base = zoneId === 'eyes' ? calibration : (this.options.ledRingDefaults?.[zoneId] ?? ROBOT_VIEWER_EMPTY_LED_RING_CALIBRATION);
9265
+ return resolveViewerLedRingCalibration(calibration.zones?.[zoneId], base);
9236
9266
  }
9237
9267
  resolveEyeRingDebugDimensions(calibration) {
9238
9268
  const variantCount = Math.max(1, Math.min(7, Math.round(calibration.variantCount)));
@@ -10652,4 +10682,5 @@ function sampleNavigationPath(points, progress) {
10652
10682
  }
10653
10683
  return points[points.length - 1]?.clone() ?? new Vector3();
10654
10684
  }
10685
+ import { resolveViewerLedRingCalibration } from '../ledRingCalibration.js';
10655
10686
  //# sourceMappingURL=Viewer3DRenderer.js.map