Skip to content

Commit fb19ec6

Browse files
committed
Implement set object animation
1 parent f7b8588 commit fb19ec6

File tree

3 files changed

+159
-4
lines changed

3 files changed

+159
-4
lines changed

Readme.md

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ where `dom_element` is the `div` in which the viewer should live. The primary in
418418
fps: 30,
419419
name: "default",
420420
tracks: [{
421-
name: ".position"
421+
name: ".position",
422422
type: "vector3",
423423
keys: [{
424424
time: 0,
@@ -435,7 +435,7 @@ where `dom_element` is the `div` in which the viewer should live. The primary in
435435
fps: 30,
436436
name: "default",
437437
tracks: [{
438-
name: ".position"
438+
name: ".position",
439439
type: "vector3",
440440
keys: [{
441441
time: 0,
@@ -446,6 +446,79 @@ where `dom_element` is the `div` in which the viewer should live. The primary in
446446
}],
447447
}]
448448
}
449+
},{
450+
path: "/meshcat/boxes/box1",
451+
clip: {
452+
fps: 30,
453+
name: "default",
454+
tracks: [{
455+
name: ".object",
456+
type: "Object",
457+
keys: [{
458+
time: 0,
459+
value: {
460+
metadata: { version: 4.5, type: "Object" },
461+
geometries: [
462+
{
463+
uuid: "cef79e52-526d-4263-b595-04fa2705974e",
464+
type: "BoxGeometry",
465+
width: 1,
466+
height: 1,
467+
depth: 1
468+
}
469+
],
470+
materials: [
471+
{
472+
uuid: "0767ae32-eb34-450c-b65f-3ae57a1102c3",
473+
type: "MeshLambertMaterial",
474+
color: 16777215,
475+
emissive: 0,
476+
side: 2,
477+
depthFunc: 3,
478+
depthTest: true,
479+
depthWrite: true
480+
}
481+
],
482+
object: {
483+
uuid: "00c2baef-9600-4c6b-b88d-7e82c40e004f",
484+
type: "Mesh",
485+
geometry: "cef79e52-526d-4263-b595-04fa2705974e",
486+
material: "0767ae32-eb34-450c-b65f-3ae57a1102c3"
487+
}
488+
}
489+
},{
490+
time: 40,
491+
value: {
492+
metadata: { version: 4.5, type: "Object" },
493+
geometries: [
494+
{
495+
uuid: "3f46ac72-c64d-4928-b6d2-dfe479845463",
496+
type: "SphereGeometry",
497+
radius: 0.6,
498+
}
499+
],
500+
materials: [
501+
{
502+
uuid: "0767ae32-eb34-450c-b65f-3ae57a1102c3",
503+
type: "MeshLambertMaterial",
504+
color: 16777215,
505+
emissive: 0,
506+
side: 2,
507+
depthFunc: 3,
508+
depthTest: true,
509+
depthWrite: true
510+
}
511+
],
512+
object: {
513+
uuid: "37f03609-0722-4367-a0bc-ddc4f2396b98",
514+
type: "Mesh",
515+
geometry: "3f46ac72-c64d-4928-b6d2-dfe479845463",
516+
material: "0767ae32-eb34-450c-b65f-3ae57a1102c3"
517+
}
518+
}
519+
}],
520+
}]
521+
}
449522
}],
450523
options: {
451524
play: true,
@@ -456,7 +529,7 @@ where `dom_element` is the `div` in which the viewer should live. The primary in
456529
</dd>
457530
<dt><code>set_target</code></dt>
458531
<dd>
459-
Set the target of the 3D camera, around which it rotates. This is expressed in a left-handed coordinate system where <emph>y</emph> is up.
532+
Set the target of the 3D camera, around which it rotates. This is expressed in a left-handed coordinate system where <emph>y</emph> is up.
460533
<p>Example:</p>
461534
<pre>
462535
{

dist/main.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,7 @@ class Animator {
957957
this.time_scrubber = null;
958958
this.setup_capturer("png");
959959
this.duration = 0;
960+
this.object_setting_animation = new ObjectSettingAnimation();
960961
}
961962

962963
setup_capturer(format) {
@@ -1021,6 +1022,7 @@ class Animator {
10211022
action.time = Math.max(0, Math.min(action._clip.duration, time));
10221023
});
10231024
this.mixer.update(0);
1025+
this.object_setting_animation.setTime(time);
10241026
this.viewer.set_dirty();
10251027
}
10261028

@@ -1030,6 +1032,7 @@ class Animator {
10301032
}
10311033
this.display_progress(0);
10321034
this.mixer.update(0);
1035+
this.object_setting_animation.setTime(0);
10331036
this.setup_capturer(this.capturer.format);
10341037
this.viewer.set_dirty();
10351038
}
@@ -1041,6 +1044,7 @@ class Animator {
10411044
this.duration = 0;
10421045
this.display_progress(0);
10431046
this.mixer = new THREE.AnimationMixer();
1047+
this.object_setting_animation = new ObjectSettingAnimation();
10441048
}
10451049

10461050
load(animations, options) {
@@ -1083,6 +1087,8 @@ class Animator {
10831087
options.clampWhenFinished = true
10841088
}
10851089

1090+
this.object_setting_animation = new ObjectSettingAnimation(this.viewer, animations);
1091+
10861092
this.duration = 0;
10871093
this.progress = 0;
10881094
for (let animation of animations) {
@@ -1116,6 +1122,7 @@ class Animator {
11161122
return Math.max(acc, action.time);
11171123
}, 0);
11181124
this.display_progress(current_time);
1125+
this.object_setting_animation.setTime(current_time);
11191126
} else {
11201127
this.display_progress(0);
11211128
}
@@ -1136,6 +1143,81 @@ class Animator {
11361143
}
11371144
}
11381145

1146+
class ObjectSettingAnimation {
1147+
constructor(viewer, animations) {
1148+
if (viewer === undefined) return;
1149+
this.viewer = viewer;
1150+
this.data = [];
1151+
if (animations === undefined) return;
1152+
1153+
let indices_to_remove = [];
1154+
for (let i = animations.length-1; i >= 0; --i) {
1155+
let animation = animations[i];
1156+
let clip = animation.clip;
1157+
1158+
let object_tracks = [];
1159+
clip.tracks = clip.tracks.filter(track => {
1160+
if (track.name == ".object") {
1161+
object_tracks.push(track);
1162+
return false;
1163+
}
1164+
return true;
1165+
});
1166+
if (clip.tracks.length === 0) indices_to_remove.push(i);
1167+
1168+
let keys = object_tracks.map(track => track.keys).flat();
1169+
keys.sort((a, b) => a.time - b.time);
1170+
this.data.push({
1171+
path: animation.path,
1172+
times: keys.map(item => item.time / clip.fps),
1173+
objects: keys.map(item => item.value),
1174+
fps: clip.fps,
1175+
});
1176+
}
1177+
for (let i of indices_to_remove) {
1178+
animations.splice(i, 1);
1179+
}
1180+
}
1181+
1182+
setTime(time) {
1183+
for (let item of this.data) {
1184+
let i = binary_search_closest(item.times, time);
1185+
if (Math.abs(item.times[i] - time) <= 0.5 / item.fps) {
1186+
this.viewer.set_object_from_json(item.path, item.objects[i]);
1187+
}
1188+
}
1189+
}
1190+
}
1191+
1192+
function binary_search_closest(arr, target) {
1193+
let low = 0;
1194+
let high = arr.length - 1;
1195+
1196+
if (target <= arr[low]) return low;
1197+
if (target >= arr[high]) return high;
1198+
1199+
while (low <= high) {
1200+
let mid = Math.floor((low + high) / 2);
1201+
if (arr[mid] === target) return mid;
1202+
1203+
if (arr[mid] < target) {
1204+
low = mid + 1;
1205+
} else {
1206+
high = mid - 1;
1207+
}
1208+
}
1209+
1210+
// After the loop, low is the insertion point.
1211+
// Check which of arr[low] or arr[low - 1] is closer to target.
1212+
if (low >= arr.length) return arr.length - 1;
1213+
if (low === 0) return 0;
1214+
1215+
const lowDiff = Math.abs(arr[low] - target);
1216+
const highDiff = Math.abs(arr[low - 1] - target);
1217+
return lowDiff < highDiff ? low : low - 1;
1218+
}
1219+
1220+
11391221
// Generates a gradient texture for defining the environment.
11401222
// Because it's a linear gradient, we can rely on OpenGL to do the
11411223
// linear interpolation between two rows of colors. However, to

0 commit comments

Comments
 (0)