Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 78 additions & 5 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,15 +331,15 @@ where `dom_element` is the `div` in which the viewer should live. The primary in
<dl>
<dt><code>name</code></dt>
<dd>
The property to be animated, with a leading <code>"."</code> (e.g. <code>".position"</code>)
The property to be animated, with a leading <code>"."</code> (e.g. <code>".position"</code>) A special case is where this field is <code>".object"</code>, allowing the entire object to be animated.
</dd>
<dt><code>type</code></dt>
<dd>
The Three.js data type of the property being animated (e.g. <code>"vector3"</code> for the <code>position</code> property)
</dd>
<dt><code>keys</code></dt>
<dd>
The keyframes of the animation. The format is a list of objects, each with a field <code>time</code> (in frames) and <code>value</code> indicating the value of the animated property at that time.
The keyframes of the animation. The format is a list of objects, each with a field <code>time</code> (in frames) and <code>value</code> indicating the value of the animated property at that time. For animating an entire object, the <code>value</code> field is the same as what you would pass to the <code>object</code> field in the <code>set_object</code> command above.
</dd>
</dl>
</dd>
Expand Down Expand Up @@ -368,7 +368,7 @@ where `dom_element` is the `div` in which the viewer should live. The primary in
fps: 30,
name: "default",
tracks: [{
name: ".position"
name: ".position",
type: "vector3",
keys: [{
time: 0,
Expand All @@ -385,7 +385,7 @@ where `dom_element` is the `div` in which the viewer should live. The primary in
fps: 30,
name: "default",
tracks: [{
name: ".position"
name: ".position",
type: "vector3",
keys: [{
time: 0,
Expand All @@ -396,6 +396,79 @@ where `dom_element` is the `div` in which the viewer should live. The primary in
}],
}]
}
},{
path: "/meshcat/boxes/box1",
clip: {
fps: 30,
name: "default",
tracks: [{
name: ".object",
type: "Object",
keys: [{
time: 0,
value: {
metadata: { version: 4.5, type: "Object" },
geometries: [
{
uuid: "cef79e52-526d-4263-b595-04fa2705974e",
type: "BoxGeometry",
width: 1,
height: 1,
depth: 1
}
],
materials: [
{
uuid: "0767ae32-eb34-450c-b65f-3ae57a1102c3",
type: "MeshLambertMaterial",
color: 16777215,
emissive: 0,
side: 2,
depthFunc: 3,
depthTest: true,
depthWrite: true
}
],
object: {
uuid: "00c2baef-9600-4c6b-b88d-7e82c40e004f",
type: "Mesh",
geometry: "cef79e52-526d-4263-b595-04fa2705974e",
material: "0767ae32-eb34-450c-b65f-3ae57a1102c3"
}
}
},{
time: 40,
value: {
metadata: { version: 4.5, type: "Object" },
geometries: [
{
uuid: "3f46ac72-c64d-4928-b6d2-dfe479845463",
type: "SphereGeometry",
radius: 0.6,
}
],
materials: [
{
uuid: "0767ae32-eb34-450c-b65f-3ae57a1102c3",
type: "MeshLambertMaterial",
color: 16777215,
emissive: 0,
side: 2,
depthFunc: 3,
depthTest: true,
depthWrite: true
}
],
object: {
uuid: "37f03609-0722-4367-a0bc-ddc4f2396b98",
type: "Mesh",
geometry: "3f46ac72-c64d-4928-b6d2-dfe479845463",
material: "0767ae32-eb34-450c-b65f-3ae57a1102c3"
}
}
}],
}]
}
}],
options: {
play: true,
Expand All @@ -406,7 +479,7 @@ where `dom_element` is the `div` in which the viewer should live. The primary in
</dd>
<dt><code>set_target</code></dt>
<dd>
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.
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.
<p>Example:</p>
<pre>
{
Expand Down
2 changes: 1 addition & 1 deletion dist/main.min.js

Large diffs are not rendered by default.

82 changes: 82 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,7 @@ class Animator {
this.time_scrubber = null;
this.setup_capturer("png");
this.duration = 0;
this.object_setting_animation = new ObjectSettingAnimation();
}

setup_capturer(format) {
Expand Down Expand Up @@ -913,6 +914,7 @@ class Animator {
action.time = Math.max(0, Math.min(action._clip.duration, time));
});
this.mixer.update(0);
this.object_setting_animation.setTime(time);
this.viewer.set_dirty();
}

Expand All @@ -922,6 +924,7 @@ class Animator {
}
this.display_progress(0);
this.mixer.update(0);
this.object_setting_animation.setTime(0);
this.setup_capturer(this.capturer.format);
this.viewer.set_dirty();
}
Expand All @@ -933,6 +936,7 @@ class Animator {
this.duration = 0;
this.display_progress(0);
this.mixer = new THREE.AnimationMixer();
this.object_setting_animation = new ObjectSettingAnimation();
}

load(animations, options) {
Expand Down Expand Up @@ -975,6 +979,8 @@ class Animator {
options.clampWhenFinished = true
}

this.object_setting_animation = new ObjectSettingAnimation(this.viewer, animations);

this.duration = 0;
this.progress = 0;
for (let animation of animations) {
Expand Down Expand Up @@ -1008,6 +1014,7 @@ class Animator {
return Math.max(acc, action.time);
}, 0);
this.display_progress(current_time);
this.object_setting_animation.setTime(current_time);
} else {
this.display_progress(0);
}
Expand All @@ -1028,6 +1035,81 @@ class Animator {
}
}

class ObjectSettingAnimation {
constructor(viewer, animations) {
if (viewer === undefined) return;
this.viewer = viewer;
this.data = [];
if (animations === undefined) return;

let indices_to_remove = [];
for (let i = animations.length-1; i >= 0; --i) {
let animation = animations[i];
let clip = animation.clip;

let object_tracks = [];
clip.tracks = clip.tracks.filter(track => {
if (track.name == ".object") {
object_tracks.push(track);
return false;
}
return true;
});
if (clip.tracks.length === 0) indices_to_remove.push(i);

let keys = object_tracks.map(track => track.keys).flat();
keys.sort((a, b) => a.time - b.time);
this.data.push({
path: animation.path,
times: keys.map(item => item.time / clip.fps),
objects: keys.map(item => item.value),
fps: clip.fps,
});
}
for (let i of indices_to_remove) {
animations.splice(i, 1);
}
}

setTime(time) {
for (let item of this.data) {
let i = binary_search_closest(item.times, time);
if (Math.abs(item.times[i] - time) <= 0.5 / item.fps) {
this.viewer.set_object_from_json(item.path, item.objects[i]);
}
}
}
}

function binary_search_closest(arr, target) {
let low = 0;
let high = arr.length - 1;

if (target <= arr[low]) return low;
if (target >= arr[high]) return high;

while (low <= high) {
let mid = Math.floor((low + high) / 2);
if (arr[mid] === target) return mid;

if (arr[mid] < target) {
low = mid + 1;
} else {
high = mid - 1;
}
}

// After the loop, low is the insertion point.
// Check which of arr[low] or arr[low - 1] is closer to target.
if (low >= arr.length) return arr.length - 1;
if (low === 0) return 0;

const lowDiff = Math.abs(arr[low] - target);
const highDiff = Math.abs(arr[low - 1] - target);
return lowDiff < highDiff ? low : low - 1;
}


// Generates a gradient texture for defining the environment.
// Because it's a linear gradient, we can rely on OpenGL to do the
// linear interpolation between two rows of colors. However, to
Expand Down