var THREE = require('three');
var JSONLoader = require('./loader').JSONLoader;
/**
* This is a container of {@link Zinc.Glyph} and their graphical properties
* including transformations, colors, number of time steps, duration of animations
* and group name. Please note that all glyphs in the glyphset share the same geometry
* however they may have different transformations.
*
* @class
* @author Alan Wu
* @return {Zinc.Glyphset}
*/
exports.Glyphset = function() {
var glyphList = [];
var axis1s = undefined;
var axis2s = undefined;
var axis3s = undefined;
var positions = undefined;
var scales = undefined;
var colors = undefined;
var labels = undefined;
var numberOfTimeSteps = 0;
var numberOfVertices = 0;
var baseSize = [0, 0, 0];
var offset = [0, 0, 0];
var scaleFactors = [ 0, 0, 0 ];
var repeat_mode = "NONE";
this.duration = 3000;
var inbuildTime = 0;
this.ready = false;
var group = new THREE.Group();
var _this = this;
var morphColours = false;
var morphVertices = false;
var groupName = undefined;
this.userData = [];
/**
* Get the {@link Three.Group} containing all of the glyphs' meshes.
* @returns {Three.Group}
*/
this.getGroup = function() {
return group;
}
/**
* Set the visibility of this glyphset.
* @param {Boolean} flag - visibility to be set for this glyphset.
*/
this.setVisibility = function(flag) {
group.visible = flag;
}
/**
* Copy glyphset data into this glyphset then load the glyph's geoemtry
* with the provided glyphURL. FinishCallback will be called once
* glyph is loaded.
*
* @param {Array} glyphsetData - contains the informations about the glyphs.
* @param {String} glyphURL - URL to the geometry which will be applied to all
* all the glyphs in the glyphset once loaded.
* @param {Function} finishCallback - User's function to be called once glyph's
* geometry is loaded.
*/
this.load = function(glyphsetData, glyphURL, finishCallback) {
axis1s = glyphsetData.axis1;
axis2s = glyphsetData.axis2;
axis3s = glyphsetData.axis3;
positions = glyphsetData.positions;
scales = glyphsetData.scale;
colors = glyphsetData.colors;
labels = glyphsetData.label;
morphColours = glyphsetData.metadata.MorphColours;
morphVertices = glyphsetData.metadata.MorphVertices;
numberOfTimeSteps = glyphsetData.metadata.number_of_time_steps;
repeat_mode = glyphsetData.metadata.repeat_mode;
numberOfVertices = glyphsetData.metadata.number_of_vertices;
if (repeat_mode == "AXES_2D" || repeat_mode == "MIRROR")
numberOfVertices = numberOfVertices * 2;
else if (repeat_mode == "AXES_3D")
numberOfVertices = numberOfVertices * 3;
baseSize = glyphsetData.metadata.base_size;
offset = glyphsetData.metadata.offset;
scaleFactors = glyphsetData.metadata.scale_factors;
var loader = new JSONLoader();
loader.load( glyphURL, meshloader(finishCallback));
}
/**
* Calculate the actual transformation value that can be applied
* to the transformation matrix.
* @returns {Array}
*/
var resolve_glyph_axes = function(point, axis1, axis2, axis3, scale)
{
var return_arrays = [];
if (repeat_mode == "NONE" || repeat_mode == "MIRROR")
{
var axis_scale = [0.0, 0.0, 0.0];
var final_axis1 = [0.0, 0.0, 0.0];
var final_axis2 = [0.0, 0.0, 0.0];
var final_axis3 = [0.0, 0.0, 0.0];
var final_point = [0.0, 0.0, 0.0];
var mirrored_axis1 = [0.0, 0.0, 0.0];
var mirrored_axis2 = [0.0, 0.0, 0.0];
var mirrored_axis3 = [0.0, 0.0, 0.0];
var mirrored_point = [0.0, 0.0, 0.0];
for (var j = 0; j < 3; j++)
{
var sign = (scale[j] < 0.0) ? -1.0 : 1.0;
axis_scale[j] = sign*baseSize[j] + scale[j]*scaleFactors[j];
}
for (var j = 0; j < 3; j++)
{
final_axis1[j] = axis1[j]*axis_scale[0];
final_axis2[j] = axis2[j]*axis_scale[1];
final_axis3[j] = axis3[j]*axis_scale[2];
final_point[j] = point[j]
+ offset[0]*final_axis1[j]
+ offset[1]*final_axis2[j]
+ offset[2]*final_axis3[j];
if (repeat_mode == "MIRROR")
{
mirrored_axis1[j] = -final_axis1[j];
mirrored_axis2[j] = -final_axis2[j];
mirrored_axis3[j] = -final_axis3[j];
mirrored_point[j] = final_point[j];
if (scale[0] < 0.0)
{
// shift glyph origin to end of axis1
final_point[j] -= final_axis1[j];
mirrored_point[j] -= mirrored_axis1[j];
}
}
}
/* if required, reverse axis3 to maintain right-handed coordinate system */
if (0.0 > (
final_axis3[0]*(final_axis1[1]*final_axis2[2] -
final_axis1[2]*final_axis2[1]) +
final_axis3[1]*(final_axis1[2]*final_axis2[0] -
final_axis1[0]*final_axis2[2]) +
final_axis3[2]*(final_axis1[0]*final_axis2[1] -
final_axis1[1]*final_axis2[0])))
{
final_axis3[0] = -final_axis3[0];
final_axis3[1] = -final_axis3[1];
final_axis3[2] = -final_axis3[2];
}
return_arrays.push([final_point, final_axis1, final_axis2, final_axis3]);
if (repeat_mode == "MIRROR")
{
if (0.0 > (
mirrored_axis3[0]*(mirrored_axis1[1]*mirrored_axis2[2] -
mirrored_axis1[2]*mirrored_axis2[1]) +
mirrored_axis3[1]*(mirrored_axis1[2]*mirrored_axis2[0] -
mirrored_axis1[0]*mirrored_axis2[2]) +
mirrored_axis3[2]*(mirrored_axis1[0]*mirrored_axis2[1] -
mirrored_axis1[1]*mirrored_axis2[0])))
{
mirrored_axis3[0] = -mirrored_axis3[0];
mirrored_axis3[1] = -mirrored_axis3[1];
mirrored_axis3[2] = -mirrored_axis3[2];
}
return_arrays.push([mirrored_point, mirrored_axis1, mirrored_axis2, mirrored_axis3]);
}
}
else if (repeat_mode == "AXES_2D" || repeat_mode == "AXES_3D")
{
var axis_scale = [0.0, 0.0, 0.0];
var final_point = [0.0, 0.0, 0.0];
for (var j = 0; j < 3; j++)
{
var sign = (scale[j] < 0.0) ? -1.0 : 1.0;
axis_scale[j] = sign*baseSize[0] + scale[j]*scaleFactors[0];
}
for (var j = 0; j < 3; j++)
{
final_point[j] = point[j]
+ offset[0]*axis_scale[0]*axis1[j]
+ offset[1]*axis_scale[1]*axis2[j]
+ offset[2]*axis_scale[2]*axis3[j];
}
var number_of_glyphs = (glyph_repeat_mode == "AXES_2D") ? 2 : 3;
for (var k = 0; k < number_of_glyphs; k++)
{
var use_axis1, use_axis2;
var use_scale = scale[k];
var final_axis1 = [0.0, 0.0, 0.0];
var final_axis2 = [0.0, 0.0, 0.0];
var final_axis3 = [0.0, 0.0, 0.0];
if (k == 0)
{
use_axis1 = axis1;
use_axis2 = axis2;
}
else if (k == 1)
{
use_axis1 = axis2;
use_axis2 = (glyph_repeat_mode == "AXES_2D") ? axis1 : axis3;
}
else // if (k == 2)
{
use_axis1 = axis3;
use_axis2 = axis1;
}
var final_scale1 = baseSize[0] + use_scale*scaleFactors[0];
final_axis1[0] = use_axis1[0]*final_scale1;
final_axis1[1] = use_axis1[1]*final_scale1;
final_axis1[2] = use_axis1[2]*final_scale1;
final_axis3[0] = final_axis1[1]*use_axis2[2] - use_axis2[1]*final_axis1[2];
final_axis3[1] = final_axis1[2]*use_axis2[0] - use_axis2[2]*final_axis1[0];
final_axis3[2] = final_axis1[0]*use_axis2[1] - final_axis1[1]*use_axis2[0];
var magnitude = Math.sqrt(final_axis3[0]*final_axis3[0] + final_axis3[1]*final_axis3[1] + final_axis3[2]*final_axis3[2]);
if (0.0 < magnitude)
{
var scaling = (baseSize[2] + use_scale*scaleFactors[2]) / magnitude;
if ((repeat_mode =="AXES_2D") && (k > 0))
{
scaling *= -1.0;
}
final_axis3[0] *= scaling;
final_axis3[1] *= scaling;
final_axis3[2] *= scaling;
}
final_axis2[0] = final_axis3[1]*final_axis1[2] - final_axis1[1]*final_axis3[2];
final_axis2[1] = final_axis3[2]*final_axis1[0] - final_axis1[2]*final_axis3[0];
final_axis2[2] = final_axis3[0]*final_axis1[1] - final_axis3[1]*final_axis1[0];
magnitude = Math.sqrt(final_axis2[0]*final_axis2[0] + final_axis2[1]*final_axis2[1] + final_axis2[2]*final_axis2[2]);
if (0.0 < magnitude)
{
var scaling = (baseSize[1] + use_scale*scaleFactors[1]) / magnitude;
final_axis2[0] *= scaling;
final_axis2[1] *= scaling;
final_axis2[2] *= scaling;
}
return_arrays.push([final_point, final_axis1, final_axis2, final_axis3])
}
}
return return_arrays;
}
/**
* Update transformation for each of the glyph in this glyphset.
*/
var updateGlyphsetTransformation = function(current_positions, current_axis1s, current_axis2s, current_axis3s,
current_scales) {
var numberOfGlyphs = 1;
if (repeat_mode == "AXES_2D" || repeat_mode == "MIRROR")
numberOfGlyphs = 2;
else if (repeat_mode == "AXES_3D")
numberOfGlyphs = 3;
var numberOfPositions = current_positions.length / 3;
var current_glyph_index = 0 ;
for (var i = 0; i < numberOfPositions; i++) {
var current_index = i * 3;
var current_position = [current_positions[current_index], current_positions[current_index+1],
current_positions[current_index+2]];
var current_axis1 = [current_axis1s[current_index], current_axis1s[current_index+1],
current_axis1s[current_index+2]];
var current_axis2 = [current_axis2s[current_index], current_axis2s[current_index+1],
current_axis2s[current_index+2]];
var current_axis3 = [current_axis3s[current_index], current_axis3s[current_index+1],
current_axis3s[current_index+2]];
var current_scale = [current_scales[current_index], current_scales[current_index+1],
current_scales[current_index+2]];
var arrays = resolve_glyph_axes(current_position, current_axis1, current_axis2,
current_axis3, current_scale);
if (arrays.length == numberOfGlyphs)
{
for (var j = 0; j < numberOfGlyphs; j++)
{
var glyph = glyphList[current_glyph_index];
if(glyph)
glyph.setTransformation(arrays[j][0], arrays[j][1], arrays[j][2], arrays[j][3]);
current_glyph_index++;
}
}
}
}
/**
* Update colour for each of the glyph in this glyphset.
*/
var updateGlyphsetHexColors = function(current_colors) {
var numberOfGlyphs = 1;
if (repeat_mode == "AXES_2D" || repeat_mode == "MIRROR")
numberOfGlyphs = 2;
else if (repeat_mode == "AXES_3D")
numberOfGlyphs = 3;
var numberOfColours = current_colors.length;
var current_glyph_index = 0 ;
for (var i = 0; i < numberOfColours; i++) {
var hex_values = current_colors[i];
for (var j = 0; j < numberOfGlyphs; j++)
{
var glyph = glyphList[current_glyph_index];
if (glyph) {
var mycolor = new THREE.Color(hex_values);
glyph.setColor(mycolor);
}
current_glyph_index++;
}
}
}
/**
* Update the current states of the glyphs in this glyphset, this includes transformation and
* colour for each of them. This is called when glyphset and glyphs are initialised and whenever
* the internal time has been updated.
*/
var updateMorphGlyphsets = function() {
var current_positions = [];
var current_axis1s = [];
var current_axis2s = [];
var current_axis3s = [];
var current_scales = [];
var current_colors = [];
var current_time = inbuildTime/_this.duration * (numberOfTimeSteps - 1);
var bottom_frame = Math.floor(current_time);
var proportion = 1 - (current_time - bottom_frame);
var top_frame = Math.ceil(current_time);
if (morphVertices) {
var bottom_positions = positions[bottom_frame.toString()];
var top_positions = positions[top_frame.toString()];
var bottom_axis1 = axis1s[bottom_frame.toString()];
var top_axis1 = axis1s[top_frame.toString()];
var bottom_axis2 = axis2s[bottom_frame.toString()];
var top_axis2 = axis2s[top_frame.toString()];
var bottom_axis3 = axis3s[bottom_frame.toString()];
var top_axis3 = axis3s[top_frame.toString()];
var bottom_scale = scales[bottom_frame.toString()];
var top_scale = scales[top_frame.toString()];
for (var i = 0; i < bottom_positions.length; i++) {
current_positions.push(proportion * bottom_positions[i] + (1.0 - proportion) * top_positions[i]);
current_axis1s.push(proportion * bottom_axis1[i] + (1.0 - proportion) * top_axis1[i]);
current_axis2s.push(proportion * bottom_axis2[i] + (1.0 - proportion) * top_axis2[i]);
current_axis3s.push(proportion * bottom_axis3[i] + (1.0 - proportion) * top_axis3[i]);
current_scales.push(proportion * bottom_scale[i] + (1.0 - proportion) * top_scale[i]);
}
} else {
current_positions = positions["0"];
current_axis1s = axis1s["0"];
current_axis2s = axis2s["0"];
current_axis3s = axis3s["0"];
current_scales = scales["0"];
}
updateGlyphsetTransformation(current_positions, current_axis1s, current_axis2s, current_axis3s,
current_scales);
if (colors != undefined) {
if (morphColours) {
var bottom_colors = colors[bottom_frame.toString()];
var top_colors = colors[top_frame.toString()];
for (var i = 0; i < bottom_colors.length; i++) {
var bot = new THREE.Color(bottom_colors[i]);
var top = new THREE.Color(top_colors[i]);
var resulting_color = new THREE.Color(bot.r * proportion + top.r * (1 - proportion),
bot.g * proportion + top.g * (1 - proportion),
bot.b * proportion + top.b * (1 - proportion));
current_colors.push(resulting_color.getHex());
}
/*
for (var i = 0; i < bottom_colors.length; i++) {
current_colors.push(proportion * bottom_colors[i] + (1.0 - proportion) * top_colors[i]);
}
*/
} else {
current_colors = colors["0"];
}
updateGlyphsetHexColors(current_colors);
}
current_positions = null;
current_axis1s = null;
current_axis2s = null;
current_axis3s = null;
current_scales = null;
current_colors = null;
}
var createGlyphs = function(geometry, material) {
for (var i = 0; i < numberOfVertices; i ++) {
var glyph = new (require('./glyph').Glyph)(geometry, material, i + 1, _this);
console.log(labels);
if (labels != undefined && labels[i] != undefined) {
glyph.setLabel(labels[i]);
}
glyphList[i] = glyph;
group.add(glyph.getGroup());
}
//Update the transformation of the glyphs.
updateGlyphsetTransformation(positions["0"], axis1s["0"],
axis2s["0"], axis3s["0"], scales["0"]);
//Update the color of the glyphs.
if (colors != undefined) {
updateGlyphsetHexColors(colors["0"]);
}
_this.ready = true;
}
/**
* A function which iterates through the list of glyphs and call the callback
* function with the glyph as the argument.
* @param {Function} callbackFunction - Callback function with the glyph
* as an argument.
*/
this.forEachGlyph = function(callbackFunction) {
for ( var i = 0; i < glyphList.length; i ++ ) {
callbackFunction(glyphList[i]);
}
}
var meshloader = function(finishCallback) {
return function(geometry, materials){
var material = undefined;
if (materials && materials[0]) {
material = materials[0];
}
createGlyphs(geometry, material);
if (finishCallback != undefined && (typeof finishCallback == 'function'))
finishCallback(_this);
}
}
/**
* Get the bounding box for the whole set of glyphs.
*
* @return {Three.Box3};
*/
this.getBoundingBox = function() {
var boundingBox1 = undefined, boundingBox2 = undefined;
for ( var i = 0; i < glyphList.length; i ++ ) {
boundingBox2 = glyphList[i].getBoundingBox();
if (boundingBox1 == undefined) {
boundingBox1 = boundingBox2;
} else {
boundingBox1.union(boundingBox2);
}
}
return boundingBox1;
}
/**
* Set the local time of this glyphset.
*
* @param {Number} time - Can be any value between 0 to duration.
*/
this.setMorphTime = function (time) {
if (time > _this.duration)
inbuildTime = _this.duration;
else if (0 > time)
inbuildTime = 0;
else
inbuildTime = time;
if (morphColours || morphVertices) {
updateMorphGlyphsets();
}
}
/**
* Check if the glyphset is time varying.
*
* @return {Boolean}
*/
this.isTimeVarying = function () {
if ((numberOfTimeSteps > 0) && (morphColours || morphVertices))
return true;
return false;
}
/**
* Clear this glyphset and its list of glyphs which will release them from the memory.
*/
this.dispose = function() {
for( var i = glyphList.length - 1; i >= 0; i--) {
glyphList[i].dispose();
}
axis1s = undefined;
axis2s = undefined;
axis3s = undefined;
positions = undefined;
scales = undefined;
colors = undefined;
_this.ready = false;
groupName = undefined;
}
//Update the geometry and colours depending on the morph.
this.render = function(delta, playAnimation) {
if (playAnimation == true)
{
var targetTime = inbuildTime + delta;
if (targetTime > _this.duration)
targetTime = targetTime - _this.duration
inbuildTime = targetTime;
if (morphColours || morphVertices) {
updateMorphGlyphsets();
}
}
}
}
Source