"use strict"; define( [ "module", "vwf/model", "vwf/utility" ], function( module, model, utility ) { // Set up render order constants for use with renderTop // Transparent objects render back to front, so use small numbers var DEPTH_GRID = Number.MIN_SAFE_INTEGER + 3; var DEPTH_AXES = Number.MIN_SAFE_INTEGER + 2; var DEPTH_OBJECTS = Number.MIN_SAFE_INTEGER + 1; var self; return model.load( module, { // == Module Definition ==================================================================== // -- initialize --------------------------------------------------------------------------- initialize: function() { self = this; this.state.graphs = {}; this.state.objects = {}; this.state.kernel = this.kernel.kernel.kernel; }, // == Model API ============================================================================ // -- creatingNode ------------------------------------------------------------------------- creatingNode: function( nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childIndex, childName, callback /* ( ready ) */ ) { var node = undefined; var kernel = this.state.kernel; var protos = getPrototypes.call( this, kernel, childExtendsID ); if ( protos && isGraph( protos ) ) { node = this.state.graphs[ childID ] = getThreeJSModel().state.nodes[ childID ]; node.graphProperties = { "graphScale": undefined, "gridInterval": undefined, "gridLineInterval": undefined, "gridLength": undefined, "xAxisVisible": undefined, "yAxisVisible": undefined, "zAxisVisible": undefined, "gridVisible": undefined, "axisOpacity": undefined, "gridOpacity": undefined, "renderTop": undefined }; node.initialized = false; } else if ( protos && isGraphObject( protos ) ) { var type = getObjectType( protos ); node = this.state.objects[ childID ] = getThreeJSModel().state.nodes[ childID ]; node.type = type; switch ( type ) { case "line": node.objectProperties = { "axis": undefined, "startValue": undefined, "endValue": undefined, "color": undefined, "opacity": undefined, "lineThickness": undefined, "renderTop": undefined }; break; case "function": node.objectProperties = { "lineFunction": undefined, "startValue": undefined, "endValue": undefined, "pointCount": undefined, "color": undefined, "opacity": undefined, "lineThickness": undefined, "renderTop": undefined }; break; case "plane": node.objectProperties = { "origin": undefined, "normal": undefined, "rotationAngle": undefined, "size": undefined, "color": undefined, "opacity": undefined, "doubleSided": undefined, "renderTop": undefined }; break; case "group": node.objectProperties = { "groupVisible": undefined, "graphObjects": undefined }; break; } node.parentGraph = this.state.graphs[ node.parentID ]; node.initialized = false; } }, // -- initializingNode --------------------------------------------------------------------- initializingNode: function( nodeID, childID, childExtendsID, childImplementsIDs, childSource, childType, childIndex, childName ) { var node; if ( this.state.graphs[ childID ] ) { node = this.state.graphs[ childID ]; createGraph( node ); node.initialized = true; } else if ( this.state.objects[ childID ] ) { node = this.state.objects[ childID ]; createObject( node ); node.initialized = true; } }, // -- deletingNode ------------------------------------------------------------------------- deletingNode: function( nodeID ) { if( nodeID ) { var childNode = this.state.objects[ nodeID ]; if( childNode ) { var threeObject = childNode.threeObject; if( threeObject && threeObject.parent ) { threeObject.parent.remove( threeObject ); } delete this.state.objects[ childNode ]; } } }, // -- addingChild -------------------------------------------------------------------------- addingChild: function( nodeID, childID, childName ) { }, // -- removingChild ------------------------------------------------------------------------ removingChild: function( nodeID, childID ) { }, // -- creatingProperty --------------------------------------------------------------------- creatingProperty: function( nodeID, propertyName, propertyValue ) { return this.initializingProperty( nodeID, propertyName, propertyValue ); }, // -- initializingProperty ----------------------------------------------------------------- initializingProperty: function( nodeID, propertyName, propertyValue ) { var value = undefined; if ( propertyValue !== undefined ) { var node = this.state.objects[ nodeID ] || this.state.graphs[ nodeID ] ; if ( node !== undefined ) { switch ( propertyName ) { default: value = this.settingProperty( nodeID, propertyName, propertyValue ); break; } } } return value; }, // TODO: deletingProperty // -- settingProperty ---------------------------------------------------------------------- settingProperty: function( nodeID, propertyName, propertyValue ) { var node; if ( this.state.graphs[ nodeID ] ) { node = this.state.graphs[ nodeID ]; if ( node.graphProperties.hasOwnProperty( propertyName ) ) { node.graphProperties[ propertyName ] = propertyValue; if ( node.initialized ) { switch ( propertyName ) { case "xAxisVisible": case "yAxisVisible": case "zAxisVisible": case "gridVisible": setGraphVisibility( node, true ); break; case "graphScale": redrawGraph( node ); redrawObjects( node.ID, this.state.objects ); break; default: redrawGraph( node ); break; } } } } else if ( this.state.objects[ nodeID ] ) { node = this.state.objects[ nodeID ]; if ( node.objectProperties.hasOwnProperty( propertyName ) ) { node.objectProperties[ propertyName ] = propertyValue; if ( node.initialized ) { redrawObject( node ); } } } }, // -- gettingProperty ---------------------------------------------------------------------- gettingProperty: function( nodeID, propertyName, propertyValue ) { var node = this.state.objects[ nodeID ]; if ( node ) { } }, // -- creatingMethod ----------------------------------------------------------------------- creatingMethod: function( nodeID, methodName, methodParameters, methodBody ) { }, // TODO: deletingMethod // -- callingMethod ------------------------------------------------------------------------ callingMethod: function( nodeID, methodName, methodParameters, methodValue ) { var node; if ( this.state.graphs[ nodeID ] ) { node = this.state.graphs[ nodeID ]; if ( methodName === "setGraphVisibility" ) { var visible = methodParameters[0]; setGraphVisibility( node, visible ); } } else if ( this.state.objects[ nodeID ] ) { node = this.state.objects[ nodeID ]; if ( methodName === "setGroupItemProperty" ) { var itemIndexList = methodParameters[ 0 ]; var itemPropertyName = methodParameters[ 1 ]; var itemPropertyValue = methodParameters[ 2 ]; for ( var i = 0; i < itemIndexList.length; i++ ) { node.threeObject.children[ 0 ].children[ itemIndexList[ i ] ][ itemPropertyName ] = itemPropertyValue; } } } }, // -- creatingEvent ------------------------------------------------------------------------ creatingEvent: function( nodeID, eventName, eventParameters ) { }, // TODO: deletingEvent // -- firingEvent -------------------------------------------------------------------------- firingEvent: function( nodeID, eventName, eventParameters ) { }, // -- executing ---------------------------------------------------------------------------- executing: function( nodeID, scriptText, scriptType ) { }, } ); function getThreeJSModel() { var threejs; threejs = vwf.models[ "vwf/model/threejs" ]; while ( threejs.model ) { threejs = threejs.model; } return threejs; } function getPrototypes( kernel, extendsID ) { var prototypes = []; var id = extendsID; while ( id !== undefined ) { prototypes.push( id ); id = kernel.prototype( id ); } return prototypes; } function isGraph( prototypes ) { var foundGraph = false; if ( prototypes ) { for ( var i = 0; i < prototypes.length && !foundGraph; i++ ) { foundGraph = ( prototypes[i] == "http://vwf.example.com/graphtool/graph.vwf" ); } } return foundGraph; } function isGraphObject( prototypes ) { var foundObject = false; if ( prototypes ) { for ( var i = 0; i < prototypes.length && !foundObject; i++ ) { foundObject = ( prototypes[i] == "http://vwf.example.com/graphtool/graphline.vwf" ) || ( prototypes[i] == "http://vwf.example.com/graphtool/graphlinefunction.vwf" ) || ( prototypes[i] == "http://vwf.example.com/graphtool/graphplane.vwf" ) || ( prototypes[i] == "http://vwf.example.com/graphtool/graphgroup.vwf" ); } } return foundObject; } function getObjectType( prototypes ) { if ( prototypes ) { for ( var i = 0; i < prototypes.length; i++ ) { if ( prototypes[i] == "http://vwf.example.com/graphtool/graphline.vwf" ) { return "line"; } else if ( prototypes[i] == "http://vwf.example.com/graphtool/graphlinefunction.vwf" ) { return "function"; } else if ( prototypes[i] == "http://vwf.example.com/graphtool/graphplane.vwf" ) { return "plane"; } else if ( prototypes[i] == "http://vwf.example.com/graphtool/graphgroup.vwf" ) { return "group"; } } } return undefined; } function createGraph( node ) { var props = node.graphProperties; var graph = generateGraph( props.graphScale, props.gridInterval, props.gridLineInterval, props.gridLength, props.xAxisVisible, props.yAxisVisible, props.zAxisVisible, props.gridVisible, props.axisOpacity, props.gridOpacity, props.renderTop ); node.threeObject.add( graph ); } function createObject( node ) { var graphScale = node.parentGraph.graphProperties.graphScale; var props = node.objectProperties; var obj = null; switch( node.type ) { case "line": obj = generateLine( graphScale, props.axis, props.startValue, props.endValue, props.color, props.opacity, props.lineThickness, props.renderTop ); break; case "function": obj = generateLineFuction( graphScale, props.lineFunction, props.startValue, props.endValue, props.pointCount, props.color, props.opacity, props.lineThickness, props.renderTop ); break; case "plane": obj = generatePlane( graphScale, props.origin, props.normal, props.rotationAngle, props.size, props.color, props.opacity, props.doubleSided, props.renderTop ); break; case "group": obj = generateGroup( graphScale, props.groupVisible, props.graphObjects ); break; } obj.visible = node.threeObject.visible; node.threeObject.add( obj ); } function redrawGraph( graph ) { var oldObj = graph.threeObject.getObjectByName( "graph" ); graph.threeObject.remove( oldObj ); if ( oldObj.children.length > 0 ) { disposeObject( oldObj ); } createGraph( graph ); } function redrawObjects( graphID, objects ) { for ( var objID in objects ) { var obj = objects[ objID ]; if ( obj.parentID === graphID && obj.initialized ) { redrawObject( obj ); } } } function redrawObject( obj ) { var oldObj = obj.threeObject.children[ 0 ]; obj.threeObject.remove( oldObj ); if ( oldObj.children.length > 0 ) { disposeObject( oldObj ); } createObject( obj ); } function disposeObject( obj ) { var child, i; for ( i = 0; i < obj.children.length; i++ ) { child = obj.children[ i ]; if ( child.children.length > 0 ) { disposeObject( child ); } else if ( child instanceof THREE.Mesh ) { if ( child.geometry ) { child.geometry.dispose(); } if ( child.material ) { if ( child.material.map ) { child.material.map.dispose(); } child.material.dispose(); } } } } function setGraphVisibility( node, value ) { var graph, xAxis, yAxis, zAxis, gridLines; graph = node.threeObject.getObjectByName( "graph" ); if ( graph ) { xAxis = graph.getObjectByName( "xAxis" ); yAxis = graph.getObjectByName( "yAxis" ); zAxis = graph.getObjectByName( "zAxis" ); gridLines = graph.getObjectByName( "gridLines" ); if ( value ) { xAxis.visible = node.graphProperties.xAxisVisible; yAxis.visible = node.graphProperties.yAxisVisible; zAxis.visible = node.graphProperties.zAxisVisible; gridLines.visible = node.graphProperties.gridVisible; for ( var line in gridLines.children ) { gridLines.children[ line ].visible = gridLines.visible; } } else { xAxis.visible = false; yAxis.visible = false; zAxis.visible = false; gridLines.visible = false; for ( var line in gridLines.children ) { gridLines.children[ line ].visible = false; } } } } function generateGraph( graphScale, gridInterval, gridLineInterval, gridLength, xAxisVisible, yAxisVisible, zAxisVisible, gridVisible, axisOpacity, gridOpacity, renderTop ) { var xAxis, yAxis, zAxis, gridX, gridY, axisLine; var thickness = 0.1; var graph = new THREE.Object3D(); var gridLines = new THREE.Object3D(); graph.name = "graph"; gridLines.name = "gridLines"; xAxis = generateLine( graphScale, [ 1, 0, 0 ], -gridLength, gridLength, [ 255, 0, 0 ], axisOpacity, thickness, renderTop ); xAxis.name = "xAxis"; xAxis.visible = xAxisVisible; yAxis = generateLine( graphScale, [ 0, 1, 0 ], -gridLength, gridLength, [ 0, 0, 255 ], axisOpacity, thickness, renderTop ); yAxis.name = "yAxis"; yAxis.visible = yAxisVisible; zAxis = generateLine( graphScale, [ 0, 0, 1 ], -gridLength, gridLength, [ 0, 255, 0 ], axisOpacity, thickness, renderTop ); zAxis.name = "zAxis"; zAxis.visible = zAxisVisible; if ( renderTop ) { xAxis.renderDepth = yAxis.renderDepth = zAxis.renderDepth = DEPTH_AXES; } graph.add( xAxis ); graph.add( yAxis ); graph.add( zAxis ); // Scale grid gridInterval *= graphScale; gridLineInterval *= graphScale; for ( var i = -gridLength * graphScale; i <= gridLength * graphScale; i += gridInterval ) { if ( i % gridLineInterval === 0 ) { thickness = 0.075; } else { thickness = 0.025; } gridX = generateLine( graphScale, [ 1, 0, 0 ], -gridLength, gridLength, [ 255, 255, 255 ], gridOpacity, thickness, renderTop ); gridX.position.set( 0, i, 0 ); gridX.visible = gridVisible; gridY = generateLine( graphScale, [ 0, 1, 0 ], -gridLength, gridLength, [ 255, 255, 255 ], gridOpacity, thickness, renderTop ); gridY.position.set( i, 0, 0 ); gridY.visible = gridVisible; if ( renderTop ) { gridX.renderDepth = gridY.renderDepth = DEPTH_GRID; } gridLines.add( gridX ); gridLines.add( gridY ); } graph.add( gridLines ); return graph; } function generateLineFuction( graphScale, functionString, startValue, endValue, pointCount, color, opacity, thickness, renderTop ) { if ( !isValidFunction( functionString ) ) { return new THREE.Mesh(); } var geometry = new THREE.Geometry(); var point, direction; var points = new Array(); var faces; var increment; var func = function( x, y, z ) { var fn = "var x = " + x + ", y = " + y + ", z = " + z + ";\n" + functionString + ";\n" + "[ x, y, z ];"; var ar = eval( fn ); x = ar[0] * graphScale; y = ar[1] * graphScale; z = ar[2] * graphScale; return new THREE.Vector3( x || 0, y || 0, z || 0 ); } increment = Math.abs( endValue - startValue ) / pointCount; // Check for endvalue + ( increment / 2 ) to account for approximation errors for ( var i = startValue; i <= endValue + ( increment / 2 ); i += increment ) { point = func( i ); direction = func( i + increment ); direction.sub( func( i - increment ) ); direction.normalize(); if ( !isNaN( point.x ) && !isNaN( point.y ) && !isNaN( point.z ) ) { var planePoints = generateLineVertices( direction, point, thickness / 2 ); for ( var j = 0; j < planePoints.length; j++ ) { points.push( planePoints[j] ); } } } geometry.vertices = points; for ( var i = 0; i < points.length; i++ ) { if ( points[i + 4] !== undefined ) { geometry.faces.push( new THREE.Face3( i, i + 4, i + 1 ) ); geometry.faces.push( new THREE.Face3( i, i + 3, i + 4 ) ); } } var last = points.length - 1; geometry.faces.push( new THREE.Face3( 0, 1, 2 ) ); geometry.faces.push( new THREE.Face3( 0, 2, 3 ) ); geometry.faces.push( new THREE.Face3( last, last - 1, last - 3 ) ); geometry.faces.push( new THREE.Face3( last - 1, last - 2, last - 3 ) ); var transparent = renderTop || opacity < 1; var vwfColor = new utility.color( color ); color = vwfColor.getHex(); var meshMaterial = new THREE.MeshBasicMaterial( { "color": color, "transparent": transparent, "opacity": opacity, "depthTest": !renderTop } ); var mesh = new THREE.Mesh( geometry, meshMaterial ); mesh.renderDepth = renderTop ? DEPTH_OBJECTS : null; return mesh; } function generateLine( graphScale, axis, startValue, endValue, color, opacity, thickness, renderTop ) { var geometry = new THREE.Geometry(); startValue *= graphScale; endValue *= graphScale; var startPoint, endPoint, direction; var points = new Array(); var planePoints, i; startPoint = new THREE.Vector3( axis[ 0 ] * startValue, axis[ 1 ] * startValue, axis[ 2 ] * startValue ); endPoint = new THREE.Vector3( axis[ 0 ] * endValue, axis[ 1 ] * endValue, axis[ 2 ] * endValue ); direction = endPoint.clone(); direction.sub( startPoint.clone() ); direction.normalize(); planePoints = generateLineVertices( direction, startPoint, thickness / 2 ); for ( i = 0; i < planePoints.length; i++ ) { points.push( planePoints[i] ); } planePoints = generateLineVertices( direction, endPoint, thickness / 2 ); for ( i = 0; i < planePoints.length; i++ ) { points.push( planePoints[i] ); } geometry.vertices = points; for ( var i = 0; i < points.length; i++ ) { if ( points[i + 4] !== undefined ) { geometry.faces.push( new THREE.Face3( i, i + 4, i + 1 ) ); geometry.faces.push( new THREE.Face3( i, i + 3, i + 4 ) ); } } var last = points.length - 1; geometry.faces.push( new THREE.Face3( 0, 1, 2 ) ); geometry.faces.push( new THREE.Face3( 0, 2, 3 ) ); geometry.faces.push( new THREE.Face3( last, last - 1, last - 3 ) ); geometry.faces.push( new THREE.Face3( last - 1, last - 2, last - 3 ) ); var transparent = renderTop || opacity < 1; var vwfColor = new utility.color( color ); color = vwfColor.getHex(); var meshMaterial = new THREE.MeshBasicMaterial( { "color": color, "transparent": transparent, "opacity": opacity, "depthTest": !renderTop } ); var mesh = new THREE.Mesh( geometry, meshMaterial ); mesh.renderDepth = renderTop ? DEPTH_OBJECTS : null; return mesh; } function generatePlane( graphScale, origin, normal, rotationAngle, size, color, opacity, doubleSided, renderTop ) { var geometry = new THREE.Geometry(); normal = new THREE.Vector3( normal[ 0 ], normal[ 1 ], normal[ 2 ] ); origin = new THREE.Vector3( origin[ 0 ], origin[ 1 ], origin[ 2 ] ); var points; size *= graphScale; origin.multiplyScalar( graphScale ); points = generatePlaneVertices( normal, origin, size ); geometry.vertices = points; geometry.faces.push( new THREE.Face3( 0, 1, 2 ) ); geometry.faces.push( new THREE.Face3( 0, 2, 3 ) ); var transparent = renderTop || opacity < 1; var vwfColor = new utility.color( color ); color = vwfColor.getHex(); var meshMaterial = new THREE.MeshBasicMaterial( { "color": color, "transparent": transparent, "opacity": opacity, "depthTest": !renderTop } ); if ( doubleSided ) { meshMaterial.side = THREE.DoubleSide; } var mesh = new THREE.Mesh( geometry, meshMaterial ); mesh.renderDepth = renderTop ? DEPTH_OBJECTS : null; return mesh; } function generateGroup( graphScale, groupVisible, graphObjects ) { var groupObject = new THREE.Object3D(); for ( var i = 0; i < graphObjects.length; i++ ) { var type = Object.keys( graphObjects[ i ] )[ 0 ]; var props = graphObjects[ i ][ type ]; var obj; switch( type ) { case "line": obj = generateLine( graphScale, props.axis, props.startValue, props.endValue, props.color, props.opacity, props.lineThickness, props.renderTop ); break; case "function": obj = generateLineFuction( graphScale, props.lineFunction, props.startValue, props.endValue, props.pointCount, props.color, props.opacity, props.lineThickness, props.renderTop ); break; case "plane": obj = generatePlane( graphScale, props.origin, props.normal, props.rotationAngle, props.size, props.color, props.opacity, props.doubleSided, props.renderTop ); break; case "group": obj = generateGroup( graphScale, props.groupVisible, props.graphObjects ); break; } obj.visible = groupVisible; groupObject.add( obj ); } return groupObject; } function generateLineVertices( normal, origin, distance ) { var vertices = new Array(); var up = new THREE.Vector3(); var left = new THREE.Vector3(); var rotAxis = new THREE.Vector3( 0, 0, 1 ); var rotAngle, rotMat; if ( Math.abs( normal.z ) === 1 ) { rotAxis.x += 0.1; rotAxis.normalize(); } rotAxis.crossVectors( normal, rotAxis ); rotAxis.normalize(); rotAngle = Math.acos( normal.dot( rotAxis ) ); rotMat = createRotationMatrix( rotAxis, rotAngle ); up.copy( normal ); up.applyMatrix3( rotMat ); up.normalize(); left.crossVectors( up, normal ); left.normalize(); vertices.push( new THREE.Vector3( origin.x + ( up.x * distance ), origin.y + ( up.y * distance ), origin.z + ( up.z * distance ) ) ); vertices.push( new THREE.Vector3( origin.x + ( left.x * distance ), origin.y + ( left.y * distance ), origin.z + ( left.z * distance ) ) ); up.negate(); left.negate(); vertices.push( new THREE.Vector3( origin.x + ( up.x * distance ), origin.y + ( up.y * distance ), origin.z + ( up.z * distance ) ) ); vertices.push( new THREE.Vector3( origin.x + ( left.x * distance ), origin.y + ( left.y * distance ), origin.z + ( left.z * distance ) ) ); return vertices; } function generatePlaneVertices( normal, origin, distance ) { var vertices = new Array(); var up = new THREE.Vector3(); var left = new THREE.Vector3(); var rotAxis = new THREE.Vector3( 0, 0, 1 ); var rotAngle, rotMat; distance /= 2; if ( Math.abs( normal.z ) === 1 ) { rotAxis.x += 0.1; rotAxis.normalize(); } rotAxis.crossVectors( normal, rotAxis ); rotAxis.normalize(); rotAngle = Math.acos( normal.dot( rotAxis ) ); rotMat = createRotationMatrix( rotAxis, rotAngle ); up.copy( normal ); up.applyMatrix3( rotMat ); up.normalize(); left.crossVectors( up, normal ); left.normalize(); vertices.push( new THREE.Vector3( origin.x + ( up.x * distance ) + ( left.x * distance ), origin.y + ( up.y * distance ) + ( left.y * distance ), origin.z + ( up.z * distance ) + ( left.z * distance ) ) ); vertices.push( new THREE.Vector3( origin.x + ( up.x * distance ) - ( left.x * distance ), origin.y + ( up.y * distance ) - ( left.y * distance ), origin.z + ( up.z * distance ) - ( left.z * distance ) ) ); up.negate(); left.negate(); vertices.push( new THREE.Vector3( origin.x + ( up.x * distance ) + ( left.x * distance ), origin.y + ( up.y * distance ) + ( left.y * distance ), origin.z + ( up.z * distance ) + ( left.z * distance ) ) ); vertices.push( new THREE.Vector3( origin.x + ( up.x * distance ) - ( left.x * distance ), origin.y + ( up.y * distance ) - ( left.y * distance ), origin.z + ( up.z * distance ) - ( left.z * distance ) ) ); return vertices; } function createRotationMatrix( axis, angle ) { var mat; var c = Math.cos( angle ); var d = 1 - c; var s = Math.sin( angle ); mat = new THREE.Matrix3(); mat.set( axis.x * axis.x * d + c, axis.x * axis.y * d + axis.z * s, axis.x * axis.z * d - axis.y * s, axis.x * axis.y * d - axis.z * s, axis.y * axis.y * d + c, axis.y * axis.z * d + axis.x * s, axis.x * axis.z * d + axis.y * s, axis.y * axis.z * d - axis.x * s, axis.z * axis.z * d + c ); return mat; } function isValidFunction( functionString ) { return ( function( x, y, z ) { var fn = "var x = " + x + ", y = " + y + ", z = " + z + ";\n" + functionString + ";\n" + "[ x, y, z ];"; try { var ar = eval( fn ); } catch ( error ) { self.logger.errorx( "generateLineFuction", error.stack ); return false; } return true; } )(); } } );