diff --git a/src/nodes/Interpolation/Interpolate.js b/src/nodes/Interpolation/Interpolate.js new file mode 100644 index 0000000..7b38c83 --- /dev/null +++ b/src/nodes/Interpolation/Interpolate.js @@ -0,0 +1,157 @@ +/** @namespace x3dom.nodeTypes */ +/* + * X3DOM JavaScript Library + * http://www.x3dom.org + * + * (C)2009 Fraunhofer IGD, Darmstadt, Germany + * Dual licensed under the MIT and GPL + */ + +// ### Interpolate ### +x3dom.registerNodeType( + "Interpolate", + "Interpolation", + defineClass(x3dom.nodeTypes.X3DInterpolatorNode, + + /** + * Constructor for Interpolate + * @constructs x3dom.nodeTypes.Interpolate + * @x3d 3.3 + * @component Interpolation + * @status full + * @extends x3dom.nodeTypes.X3DInterpolatorNode + * @param {Object} [ctx=null] - context object, containing initial settings like namespace + * @classdesc The Interpolate node is a field of Animate that performs all interpolations without using events to trigger the interpolator. The output is determined by the 'space' field... linearly interpolates among a list of 3D vectors to produce an SFVec3f value_changed event. The keyValue field shall contain exactly as many values as in the key field. + */ + function (ctx) { + x3dom.nodeTypes.Interpolate.superClass.call(this, ctx); // Not sure this is required. Not sure how to ensure this node is a child/field of Animate. + +/* + if (ctx) + ctx.doc._nodeBag.timer.push(this); + else + x3dom.debug.logWarning("Animate: No runtime context found!"); +*/ + + /** + * Defines the set of data points, that are used for interpolation. + * @var {x3dom.fields.MFVec3f} keyValue + * @memberof x3dom.nodeTypes.Interpolate + * @initvalue [] + * @field x3d + * @instance + * + * Need to make sure 'key' and 'keyValue' are child fields of Interpolate + */ + this.addField_MFFloat(ctx, 'key', []); + + /** + * Defines the destination node name for the ouput of the interpolation. The node must exist and be DEFed + * @var {x3dom.fields.MFVec3f} destinationNode + * @memberof x3dom.nodeTypes.Interpolate + * @initvalue '' + * @field x3dv4 + * @instance + */ + this.addField_SFString(ctx, 'destinationNode', ''); + + /** + * Defines the field of the destinationNode that is to be interpolated. The field must be of the datatype of the interpolator + * @var {x3dom.fields.MFVec3f} destinationField + * @memberof x3dom.nodeTypes.Interpolate + * @initvalue '' + * @field x3dv4 + * @instance + */ + this.addField_SFString(ctx, 'destinationField', []); + + /** + * Defines the interpolation algorithm + * @var {x3dom.fields.MFVec3f} algorithm + * @memberof x3dom.nodeTypes.Interpolate + * @initvalue 'LINEAR' + * @field x3dv4 + * @instance + */ + this.addField_SFString(ctx, 'algorithm', 'LINEAR'); + + /** + * Defines the output space of the interpolation + * @var {x3dom.fields.MFVec3f} space + * @memberof x3dom.nodeTypes.Interpolate + * @initvalue '3D' + * @field x3dv4 + * @instance + * + * Options are '3D', '2D', '1D', 'COLOR', 'ROTATION' + */ + this.addField_SFString(ctx, 'space', '3D'); + + /** + * Turns on or off the interpolator + * @var {x3dom.fields.MFVec3f} enabled + * @memberof x3dom.nodeTypes.Interpolate + * @initvalue true + * @field x3dv4 + * @instance + */ + this.addField_SFBool(ctx, 'enabled', true); + + if (this._vf.space == '1D') { + this.addField_MFFloat(ctx, 'keyValue', []); + this.interpolation = function(a,b,t) { + return (1.0-t)*a + t*b; + }; + } + else if (this._vf.space == '3D') { + this.addField_MFVec3f(ctx, 'keyValue', []); + this.interpolation = function(a,b,t) { + return a.multiply(1.0-t).add(b.multiply(t)); + }; + } + else if (this._vf.space == 'COLOR') { + this.addField_MFColor(ctx, 'keyValue', []); + this.interpolation = function(a,b,t) { + return a.multiply(1.0-t).add(b.multiply(t)); + }; + } + else if (this._vf.space == 'ROTATION') { + this.addField_MFRotation(ctx, 'keyValue', []); + this.interpolation = function(a,b,t) { + return a.slerp(b, t); + }; + } + + this.toNodeElement = this._nameSpace.defMap[this._vf.destinationNode]._xmlNode; + }, + /** + * This is for the incoming event -- which there isn't any. Perhaps a callback needs to be entered into the Animate object + * for each Interpolate node. Then each Animation time tick cycles through the registered callbacks + */ + + { + doInterpolate: function (ftime) + { + if (!this._vf.enabled) { + return false; + } + + var value = this.linearInterp(ftime, this.interpolation); + + //console.log ('Interpolate ftime: ' + ftime + '; value: ' + value + '\n'); + this.toNodeElement.setFieldValue(this._vf.destinationField, value); + }, + fieldChanged: function(fieldName) + { + if(fieldName === "set_fraction") + { + var value = this.linearInterp(this._vf.set_fraction, function (a, b, t) { + return a.multiply(1.0-t).add(b.multiply(t)); + }); + + this.postMessage('value_changed', value); + } + } + } + ) +); \ No newline at end of file diff --git a/src/nodes/Time/Animate.js b/src/nodes/Time/Animate.js new file mode 100644 index 0000000..0352f2b --- /dev/null +++ b/src/nodes/Time/Animate.js @@ -0,0 +1,400 @@ +/** @namespace x3dom.nodeTypes */ +/* + * X3DOM JavaScript Library + * http://www.x3dom.org + * + * (C)2009 Fraunhofer IGD, Darmstadt, Germany + * Dual licensed under the MIT and GPL + */ + +// ### Animate ### +x3dom.registerNodeType( + "Animate", + "Time", + defineClass(x3dom.nodeTypes.X3DSensorNode, + + /** + * Constructor for Animate + * @constructs x3dom.nodeTypes.Animate + * @x3d 3.3 + * @component Time + * @status full + * @extends x3dom.nodeTypes.X3DSensorNode + * @param {Object} [ctx=null] - context object, containing initial settings like namespace + * @classdesc Animate nodes generate events as time passes. + */ + function (ctx) { + x3dom.nodeTypes.Animate.superClass.call(this, ctx); + + if (ctx) + ctx.doc._nodeBag.timer.push(this); + else + x3dom.debug.logWarning("Animate: No runtime context found!"); + + + /** + * The "cycle" of a Animate node lasts for cycleInterval seconds. The value of cycleInterval shall be greater than zero. + * @var {x3dom.fields.SFTime} cycleInterval + * @range [0, inf] + * @memberof x3dom.nodeTypes.Animate + * @initvalue 1 + * @field x3d + * @instance + */ + this.addField_SFTime(ctx, 'cycleInterval', 1); + + + /** + * Specifies whether the timer cycle loops. + * @var {x3dom.fields.SFBool} loop + * @memberof x3dom.nodeTypes.Animate + * @initvalue false + * @field x3d + * @instance + */ + this.addField_SFBool(ctx, 'loop', false); + + /** + * Sets the startTime for the cycle. + * @var {x3dom.fields.SFTime} startTime + * @memberof x3dom.nodeTypes.Animate + * @initvalue 0 + * @field x3d + * @instance + */ + this.addField_SFTime(ctx, 'startTime', 0); + + /** + * Sets a time for the timer to stop. + * @var {x3dom.fields.SFTime} stopTime + * @memberof x3dom.nodeTypes.Animate + * @initvalue 0 + * @field x3d + * @instance + */ + this.addField_SFTime(ctx, 'stopTime', 0); + + /** + * Sets a time for the timer to pause. + * @var {x3dom.fields.SFTime} pauseTime + * @memberof x3dom.nodeTypes.Animate + * @initvalue 0 + * @field x3d + * @instance + */ + this.addField_SFTime(ctx, 'pauseTime', 0); + + /** + * Sets a time for the timer to resume from pause. + * @var {x3dom.fields.SFTime} resumeTime + * @memberof x3dom.nodeTypes.Animate + * @initvalue 0 + * @field x3d + * @instance + */ + this.addField_SFTime(ctx, 'resumeTime', 0); + + + /** + * A cycleTime outputOnly field can be used for synchronization purposes such as sound with animation. + * The value of a cycleTime event will be equal to the time at the beginning of the current cycle. A cycleTime event is generated at the beginning of every cycle, including the cycle starting at startTime. + * The first cycleTime event for a Animate node can be used as an alarm (single pulse at a specified time). + * @var {x3dom.fields.SFTime} cycleTime + * @memberof x3dom.nodeTypes.Animate + * @initvalue 0 + * @field x3d + * @instance + * + * Not a field (event) for Animation. Leftover from TimeSensor. + */ + //this.addField_SFTime(ctx, 'cycleTime', 0); + + /** + * The elapsedTime outputOnly field delivers the current elapsed time since the Animate was activated and running, cumulative in seconds and not counting any time while in a paused state. + * @var {x3dom.fields.SFTime} elapsedTime + * @memberof x3dom.nodeTypes.Animate + * @initvalue 0 + * @field x3d + * @instance + * + * Not a field (event) for Animation. Leftover from TimeSensor. + */ + //this.addField_SFTime(ctx, 'elapsedTime', 0); + + /** + * fraction_changed events output a floating point value in the closed interval [0, 1]. At startTime the value of fraction_changed is 0. After startTime, the value of fraction_changed in any cycle will progress through the range (0.0, 1.0]. + * @var {x3dom.fields.SFFloat} fraction_changed + * @memberof x3dom.nodeTypes.Animate + * @initvalue 0 + * @field x3d + * @instance + * + * Not a field (event) for Animation. Leftover from TimeSensor. + */ + //this.addField_SFFloat(ctx, 'fraction_changed', 0); + + /** + * Outputs whether the timer is active. + * @var {x3dom.fields.SFBool} isActive + * @memberof x3dom.nodeTypes.Animate + * @initvalue false + * @field x3d + * @instance + */ + this.addField_SFBool(ctx, 'isActive', false); + + /** + * Outputs whether the timer is paused. + * @var {x3dom.fields.SFBool} isPaused + * @memberof x3dom.nodeTypes.Animate + * @initvalue false + * @field x3d + * @instance + */ + this.addField_SFBool(ctx, 'isPaused', false); + + /** + * The time event sends the absolute time for a given tick of the Animate node. + * @var {x3dom.fields.SFTime} time + * @memberof x3dom.nodeTypes.Animate + * @initvalue 0 + * @field x3d + * @instance + * + * Field (event) not implemented for Animation. May not exist in final form. + */ + //this.addField_SFTime(ctx, 'time', 0); + + /** + * Starts Animate -- inputOnly + * @var {x3dom.fields.SFBool} start + * @memberof x3dom.nodeTypes.Animate + * @initvalue true + * @field x3dv4 + * @instance + */ + this.addField_SFBool(ctx, 'start', true); + + /** + * Stop Animate -- inputOnly + * @var {x3dom.fields.SFBool} stop + * @memberof x3dom.nodeTypes.Animate + * @initvalue false + * @field x3dv4 + * @instance + */ + this.addField_SFBool(ctx, 'stop', false); + + /** + * Animation maximum frame rate (frames per second) + * @var {x3dom.fields.SFBool} maxFrameRate + * @memberof x3dom.nodeTypes.Animate + * @initvalue 30 + * @field x3dv4 + * @instance + */ + this.addField_SFFloat(ctx, 'maxFrameRate', 30); + + /** + * Interpolator nodes + * @var {x3dom.fields.SFBool} interpolate + * @memberof x3dom.nodeTypes.Animate + * @initvalue [] + * @field x3dv4 + * @instance + */ + this.addField_MFNode('interpolate', x3dom.nodeTypes.X3DInterpolatorNode); + + /** + * + * @var {x3dom.fields.SFBool} first + * @memberof x3dom.nodeTypes.Animate + * @initvalue true + * @field x3dom + * @instance + */ + this.addField_SFBool(ctx,'first', true); + + /** + * + * @var {x3dom.fields.SFFloat} firstCycle + * @memberof x3dom.nodeTypes.Animate + * @initvalue 0.0 + * @field x3dom + * @instance + */ + this.addField_SFFloat(ctx,'firstCycle', 0.0); + + this._prevCycle = -1; + this._lastTime = 0; + this._cycleStopTime = 0; + this._activatedTime = 0; + + if (this._vf.startTime > 0) { + this._updateCycleStopTime(); + } + + this._backupStartTime = this._vf.startTime; + this._backupStopTime = this._vf.stopTime; + this._backupCycleInterval = this._vf.cycleInterval; + this._frameInterval = 1.0/Math.max(1.0, Math.min(1000.0, this._vf.maxFrameRate)); + + }, + { + tick: function (time) + { + if (!this._vf.enabled) { + this._lastTime = time; + return false; + } + + delta = time-this._lastRenderedTime; +// console.log ('Delta = ' + delta + '; min Interval = ' + this._frameInterval + '\n'); + if (delta < this._frameInterval) { + return false; + } +// console.log ('Animate at time ' + time+'\n'); + this._lastRenderedTime = time; + + var isActive = ( this._vf.cycleInterval > 0 && + time >= this._vf.startTime && + (time < this._vf.stopTime || this._vf.stopTime <= this._vf.startTime) && + (this._vf.loop == true || (this._vf.loop == false && time < this._cycleStopTime)) ); + + if (isActive && !this._vf.isActive) { + this.postMessage('isActive', true); + this._activatedTime = time; + } + + // Checking for this._vf.isActive allows the dispatch of 'final events' (before deactivation) + if (isActive || this._vf.isActive) { + //this.postMessage('elapsedTime', time - this._activatedTime); + + var isPaused = ( time >= this._vf.pauseTime && this._vf.pauseTime > this._vf.resumeTime ); + + if (isPaused && !this._vf.isPaused) { + this.postMessage('isPaused', true); + //this.postMessage('pauseTime', time); + } else if (!isPaused && this._vf.isPaused) { + this.postMessage('isPaused', false); + //this.postMessage('resumeTime', time); + } + + if (!isPaused) { + var cycleFrac = this._getCycleAt(time); + var cycle = Math.floor(cycleFrac); + + var cycleTime = this._vf.startTime + cycle*this._vf.cycleInterval; + var adjustTime = 0; + + if (this._vf.stopTime > this._vf.startTime && + this._lastTime < this._vf.stopTime && time >= this._vf.stopTime) + adjustTime = this._vf.stopTime; + else if (this._lastTime < cycleTime && time >= cycleTime) + adjustTime = cycleTime; + + if( adjustTime > 0 ) { + time = adjustTime; + cycleFrac = this._getCycleAt(time); + cycle = Math.floor(cycleFrac); + } + + var fraction = cycleFrac - cycle; + //console.log ('Animate at time ' + time + ' -- ' + fraction + '\n'); + + for (ii=0; ii + + + + + Animate node Test - 3D the HTML Way + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +