Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fast Animation #724

Merged
merged 14 commits into from
Aug 16, 2019
Merged
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
8 changes: 8 additions & 0 deletions emperor/support_files/js/animate.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ function(_, trajectory) {
* @default -1
*/
this.currentFrame = -1;

/**
* @type {Integer}
* The previous frame served by the director
*/
this.previousFrame = -1;

/**
* @type {Array}
* Array where each element in the trajectory is a trajectory with the
Expand Down Expand Up @@ -276,6 +283,7 @@ function(_, trajectory) {
*/
AnimationDirector.prototype.updateFrame = function() {
if (this.animationCycleFinished() === false) {
this.previousFrame = this.currentFrame;
this.currentFrame = this.currentFrame + 1;
}
};
Expand Down
58 changes: 47 additions & 11 deletions emperor/support_files/js/animations-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ define([
], function($, _, DecompositionView, ViewControllers, AnimationDirector,
draw, Color, ColorViewController) {
var EmperorViewController = ViewControllers.EmperorViewController;
var drawTrajectoryLine = draw.drawTrajectoryLine;
var drawTrajectoryLineStatic = draw.drawTrajectoryLineStatic;
var drawTrajectoryLineDynamic = draw.drawTrajectoryLineDynamic;
var disposeTrajectoryLineStatic = draw.disposeTrajectoryLineStatic;
var disposeTrajectoryLineDynamic = draw.disposeTrajectoryLineDynamic;
var updateStaticTrajectoryDrawRange = draw.updateStaticTrajectoryDrawRange;
var ColorEditor = Color.ColorEditor, ColorFormatter = Color.ColorFormatter;

/**
Expand Down Expand Up @@ -440,13 +444,22 @@ define([
this.playing = false;
this.director = null;

view.tubes.forEach(function(tube) {
if (tube.parent !== null) {
view.staticTubes.forEach(function(tube) {
if (tube !== null && tube.parent !== null) {
tube.parent.remove(tube);
disposeTrajectoryLineStatic(tube);
}
});
view.dynamicTubes.forEach(function(tube) {
if (tube !== null && tube.parent !== null) {
tube.parent.remove(tube);
disposeTrajectoryLineDynamic(tube);
}
});

view.tubes = [];
view.staticTubes = [];
view.dynamicTubes = [];

view.needsUpdate = true;

this._updateButtons();
Expand Down Expand Up @@ -538,20 +551,43 @@ define([
var radius = view.getGeometryFactor();
radius *= 0.45 * this.getRadius();

view.tubes.forEach(function(tube) {
if (tube === undefined) {
for (var i = 0; i < this.director.trajectories.length; i++) {
var trajectory = this.director.trajectories[i];

//Ensure static tubes are constructed
if (view.staticTubes[i] === null || view.staticTubes[i] === undefined)
{
var color = this._colors[trajectory.metadataCategoryName] || 'red';
view.staticTubes[i] = drawTrajectoryLineStatic(trajectory,
color,
radius);
}

//Ensure static tube draw ranges are set to visible segment
updateStaticTrajectoryDrawRange(trajectory,
this.director.currentFrame,
view.staticTubes[i]);
}

//Remove any old dynamic tubes from the scene
view.dynamicTubes.forEach(function(tube) {
if (tube === undefined || tube === null) {
return;
}
if (tube.parent !== null) {
tube.parent.remove(tube);
disposeTrajectoryLineDynamic(tube);
}
});

view.tubes = this.director.trajectories.map(function(trajectory) {
color = scope._colors[trajectory.metadataCategoryName] || 'red';

var tube = drawTrajectoryLine(trajectory, scope.director.currentFrame,
color, radius);
//Construct new dynamic tubes containing necessary
//interpolated segment for the current frame
view.dynamicTubes = this.director.trajectories.map(function(trajectory) {
var color = scope._colors[trajectory.metadataCategoryName] || 'red';
var tube = drawTrajectoryLineDynamic(trajectory,
scope.director.currentFrame,
color,
radius);
return tube;
});

Expand Down
103 changes: 95 additions & 8 deletions emperor/support_files/js/draw.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
/** @module draw */
define(['underscore', 'three', 'jquery'], function(_, THREE, $) {

var NUM_TUBE_SEGMENTS = 3;
var NUM_TUBE_CROSS_SECTION_POINTS = 10;

// useful for some calculations
var ZERO = new THREE.Vector3();

Expand Down Expand Up @@ -275,32 +279,111 @@ define(['underscore', 'three', 'jquery'], function(_, THREE, $) {
return arrow;
}

function drawTrajectoryLine(trajectory, currentFrame, color, radius) {
/**
* Returns a new trajectory line dynamic mesh
*/
function drawTrajectoryLineDynamic(trajectory, currentFrame, color, radius) {
// based on the example described in:
// https://github.com/mrdoob/three.js/wiki/Drawing-lines
var material, points = [], lineGeometry, limit = 0, path;

_trajectory = trajectory.representativeCoordinatesAtIndex(currentFrame);
_trajectory = trajectory.representativeInterpolatedCoordinatesAtIndex(
currentFrame);
if (_trajectory === null || _trajectory.length == 0)
return null;

material = new THREE.MeshPhongMaterial({color: color});
material.matrixAutoUpdate = true;
material.transparent = false;
material = new THREE.MeshPhongMaterial({
color: color,
transparent: false});

for (var index = 0; index < _trajectory.length; index++) {
points.push(new THREE.Vector3(_trajectory[index].x,
_trajectory[index].y, _trajectory[index].z));
}

path = new THREE.EmperorTrajectory(points);

// the line will contain the two vertices and the described material
// we increase the number of points to have a smoother transition on
// edges i. e. where the trajectory changes the direction it is going
lineGeometry = new THREE.TubeGeometry(path, (points.length - 1) * 3, radius,
dhakim87 marked this conversation as resolved.
Show resolved Hide resolved
10, false);
lineGeometry = new THREE.TubeGeometry(path,
(points.length - 1) * NUM_TUBE_SEGMENTS,
radius,
NUM_TUBE_CROSS_SECTION_POINTS,
false);

return new THREE.Mesh(lineGeometry, material);
}

/**
* Disposes a trajectory line dynamic mesh
*/
function disposeTrajectoryLineDynamic(mesh) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of the dispose functions do exactly the same thing, perhaps worth merging them into disposeTrajectoryLine?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was torn on this too. I split them because the underlying geometry and material objects inside of the mesh are what needs to be disposed, and those are both shareable across mesh instances. I was leaning towards a pattern where we put dispose (destructor) next to draw (constructor), so that changes in one function can be easily mirrored to the other. If we have no short term plan to add geometry or material reuse and want to add as a rule instead that these are copied in every mesh, I think we should use a single function for disposing arbitrary meshes, which I think is just as good.

On a side note, we're technically leaking Axis meshes when switching axes, so I figured a dispose method per type of scene object made sense when I realized I didn't want to trace the code to find out how axis meshes were initialized.

mesh.geometry.dispose();
mesh.material.dispose();
}

/**
* Returns a new trajectory line static mesh
*/
function drawTrajectoryLineStatic(trajectory, color, radius) {
var _trajectory = trajectory.coordinates;

var material = new THREE.MeshPhongMaterial({
color: color,
transparent: false}
);

var allPoints = [];
for (var index = 0; index < _trajectory.length; index++) {
allPoints.push(new THREE.Vector3(_trajectory[index].x,
_trajectory[index].y, _trajectory[index].z));
}

var path = new THREE.EmperorTrajectory(allPoints);

//Tubes are straight segments, but adding vertices along them might change
//lighting effects under certain models and lighting conditions.
var tubeBufferGeom = new THREE.TubeBufferGeometry(
path,
(allPoints.length - 1) * NUM_TUBE_SEGMENTS,
radius,
NUM_TUBE_CROSS_SECTION_POINTS,
false);

return new THREE.Mesh(tubeBufferGeom, material);
}

/**
* Disposes a trajectory line static mesh
*/
function disposeTrajectoryLineStatic(mesh) {
mesh.geometry.dispose();
mesh.material.dispose();
}

function updateStaticTrajectoryDrawRange(trajectory, currentFrame, threeMesh)
{
//Reverse engineering the number of points in a THREE tube is not fun, and
//may be implementation/version dependent.
//Number of points drawn per tube segment =
// 2 (triangles) * 3 (points per triangle) * NUM_TUBE_CROSS_SECTION_POINTS
//Number of tube segments per pair of consecutive points =
// NUM_TUBE_SEGMENTS

var multiplier = 2 * 3 * NUM_TUBE_CROSS_SECTION_POINTS * NUM_TUBE_SEGMENTS;
if (currentFrame < trajectory._intervalValues.length)
{
var intervalValue = trajectory._intervalValues[currentFrame];
threeMesh.geometry.setDrawRange(0, intervalValue * multiplier);
}
else
{
threeMesh.geometry.setDrawRange(0,
(trajectory.coordinates.length - 1) * multiplier);
}
}


/**
*
Expand Down Expand Up @@ -414,6 +497,10 @@ define(['underscore', 'three', 'jquery'], function(_, THREE, $) {

return {'formatSVGLegend': formatSVGLegend, 'makeLine': makeLine,
'makeLabel': makeLabel, 'makeArrow': makeArrow,
'drawTrajectoryLine': drawTrajectoryLine,
'drawTrajectoryLineStatic': drawTrajectoryLineStatic,
'disposeTrajectoryLineStatic': disposeTrajectoryLineStatic,
'drawTrajectoryLineDynamic': drawTrajectoryLineDynamic,
'disposeTrajectoryLineDynamic': disposeTrajectoryLineDynamic,
'updateStaticTrajectoryDrawRange': updateStaticTrajectoryDrawRange,
'makeLineCollection': makeLineCollection};
});
5 changes: 3 additions & 2 deletions emperor/support_files/js/sceneplotview3d.js
Original file line number Diff line number Diff line change
Expand Up @@ -677,8 +677,9 @@ define([
});

_.each(this.decViews, function(view) {
view.tubes.forEach(function(tube) {
scope.scene.add(tube);
view.getTubes().forEach(function(tube) {
if (tube !== null)
scope.scene.add(tube);
});
});

Expand Down
29 changes: 29 additions & 0 deletions emperor/support_files/js/trajectory.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,35 @@ define([
return output;
};

/**
*
* Grab only the interpolated portion of representativeCoordinatesAtIndex.
*
* @param {integer} idx Value for which to determine the required number of
* points.
*
* @return {Array[]} Array containing the representative float x, y, z
* coordinates needed to draw the interpolated portion of a trajectory at the
* given index.
*/
TrajectoryOfSamples.prototype.representativeInterpolatedCoordinatesAtIndex =
function(idx) {
if (idx === 0)
return null;
if (this.interpolatedCoordinates.length - 1 <= idx)
return null;

lastStaticPoint = this.coordinates[this._intervalValues[idx]];
interpPoint = this.interpolatedCoordinates[idx];
if (lastStaticPoint.x === interpPoint.x &&
lastStaticPoint.y === interpPoint.y &&
lastStaticPoint.z === interpPoint.z) {
return null; //Shouldn't pass on a zero length segment
}

return [lastStaticPoint, interpPoint];
};

/**
*
* Function to interpolate a certain number of steps between two three
Expand Down
20 changes: 18 additions & 2 deletions emperor/support_files/js/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,18 @@ function DecompositionView(decomp, asPointCloud) {
*/
this.backgroundColor = '#000000';
/**
* Tube objects on screen (used for animations)
* Static tubes objects covering an entire trajectory.
* Can use setDrawRange on the underlying geometry to display
* just part of the trajectory.
* @type {THREE.Mesh[]}
*/
this.tubes = [];
this.staticTubes = [];
/**
* Dynamic tubes covering the final tube segment of a trajectory
* Must be rebuilt each frame by the animations controller
* @type {THREE.Mesh[]}
*/
this.dynamicTubes = [];
/**
* Array of THREE.Mesh objects on screen (represent samples).
* @type {THREE.Mesh[]}
Expand Down Expand Up @@ -120,6 +128,14 @@ DecompositionView.prototype.getGeometryFactor = function() {
this.decomp.dimensionRanges.min[0]) * 0.012;
};

/**
* Retrieve a shallow copy of concatenated static and dynamic tube arrays
* @type {THREE.Mesh[]}
*/
DecompositionView.prototype.getTubes = function() {
return this.staticTubes.concat(this.dynamicTubes);
};

/**
*
* Helper method to initialize the base THREE.js objects.
Expand Down
12 changes: 8 additions & 4 deletions tests/javascript_tests/test_decomposition_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ requirejs([
equal(dv.getVisibleCount(), 2, 'visibleCount set correctly');
deepEqual(dv.visibleDimensions, [0, 1, 2],
'visibleDimensions set correctly');
deepEqual(dv.tubes, [], 'tubes set correctly');
deepEqual(dv.staticTubes, [], 'tubes set correctly');
deepEqual(dv.dynamicTubes, [], 'tubes set correctly');

equal(dv.axesColor, '#FFFFFF');
equal(dv.backgroundColor, '#000000');
Expand Down Expand Up @@ -138,7 +139,8 @@ requirejs([
equal(dv.getVisibleCount(), 2, 'visibleCount set correctly');
deepEqual(dv.visibleDimensions, [0, 1],
'visibleDimensions set correctly');
deepEqual(dv.tubes, [], 'tubes set correctly');
deepEqual(dv.staticTubes, [], 'tubes set correctly');
deepEqual(dv.dynamicTubes, [], 'tubes set correctly');

equal(dv.axesColor, '#FFFFFF');
equal(dv.backgroundColor, '#000000');
Expand All @@ -160,7 +162,8 @@ requirejs([
equal(view.count, 2);
equal(view.getVisibleCount(), 2);
deepEqual(view.visibleDimensions, [0, 1, 2]);
deepEqual(view.tubes, []);
deepEqual(view.staticTubes, [], 'tubes set correctly');
deepEqual(view.dynamicTubes, [], 'tubes set correctly');
equal(view.axesColor, '#FFFFFF');
equal(view.backgroundColor, '#000000');
deepEqual(view.axesOrientation, [1, 1, 1]);
Expand Down Expand Up @@ -199,7 +202,8 @@ requirejs([
equal(view.count, 2);
equal(view.getVisibleCount(), 2);
deepEqual(view.visibleDimensions, [0, 1, 2]);
deepEqual(view.tubes, []);
deepEqual(view.staticTubes, [], 'tubes set correctly');
deepEqual(view.dynamicTubes, [], 'tubes set correctly');
equal(view.axesColor, '#FFFFFF');
equal(view.backgroundColor, '#000000');
deepEqual(view.axesOrientation, [1, 1, 1]);
Expand Down
Loading