From d1825421538c81dd23393caad3c313a559452aa1 Mon Sep 17 00:00:00 2001 From: Oleg Kalachev Date: Wed, 12 Feb 2020 20:40:28 +0300 Subject: [PATCH 01/14] aruco.launch: set default corner refinement method to 2 (contour) --- clover/launch/aruco.launch | 1 + 1 file changed, 1 insertion(+) diff --git a/clover/launch/aruco.launch b/clover/launch/aruco.launch index 0db4c4b9..164ae2d1 100644 --- a/clover/launch/aruco.launch +++ b/clover/launch/aruco.launch @@ -10,6 +10,7 @@ + From bda966bc901d0c94d67bb05f2a0a8ebedbd05969 Mon Sep 17 00:00:00 2001 From: Oleg Kalachev Date: Wed, 12 Feb 2020 20:48:29 +0300 Subject: [PATCH 02/14] Simplify camera orientation setting (#204) * main_camera.launch: simplify camera orientation setting * Fix camera transforms * Move camera transform description closer to transform tempalte * orientation => direction * Fix --- clover/launch/main_camera.launch | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/clover/launch/main_camera.launch b/clover/launch/main_camera.launch index 6faf2c30..ad17b443 100644 --- a/clover/launch/main_camera.launch +++ b/clover/launch/main_camera.launch @@ -1,20 +1,18 @@ + + + + + + + + + + + - - - - - - - - - - - - - - + From d2b9ec71669a7ed3f6af8a76981956b50455203a Mon Sep 17 00:00:00 2001 From: Oleg Kalachev Date: Thu, 13 Feb 2020 07:37:56 +0300 Subject: [PATCH 03/14] Remove unused fpv_camera.launch --- clover/launch/fpv_camera.launch | 6 ------ clover/src/{fpv_camera => camera_stream} | 4 ++-- 2 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 clover/launch/fpv_camera.launch rename clover/src/{fpv_camera => camera_stream} (65%) diff --git a/clover/launch/fpv_camera.launch b/clover/launch/fpv_camera.launch deleted file mode 100644 index d436cd25..00000000 --- a/clover/launch/fpv_camera.launch +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/clover/src/fpv_camera b/clover/src/camera_stream similarity index 65% rename from clover/src/fpv_camera rename to clover/src/camera_stream index 052d22d8..af7079e7 100755 --- a/clover/src/fpv_camera +++ b/clover/src/camera_stream @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Usage -# fpv_camera +# camera_stream -echo "Starting FPV camera $1 on :$2" +echo "Starting camera stream $1 on :$2" mjpg_streamer -i "/usr/lib/input_uvc.so -d $1 -r 320x240 -f 30" -o "/usr/lib/output_http.so -w /usr/share/mjpg_streamer/www -p $2" From 099e115def17e482b2c1b7aa9b5424bfc5d70d4a Mon Sep 17 00:00:00 2001 From: Arthur Golubtsov Date: Thu, 13 Feb 2020 10:10:24 +0300 Subject: [PATCH 04/14] Fix typo --- docs/ru/connection.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ru/connection.md b/docs/ru/connection.md index aa61275f..0f6dc551 100644 --- a/docs/ru/connection.md +++ b/docs/ru/connection.md @@ -22,7 +22,7 @@ -Дополнительным способом подключения является подключение подключение по интерйсу UART. +Дополнительным способом подключения является подключение подключение по интерфейсу UART. 1. Подключите Raspberry Pi к полетному контроллеру по UART. 2. [Подключитесь в Raspberry Pi по SSH](ssh.md). From fca584cefe52d0e843607cf72fc29050e4c769fe Mon Sep 17 00:00:00 2001 From: Oleg Kalachev Date: Thu, 13 Feb 2020 20:00:11 +0300 Subject: [PATCH 05/14] optical_flow: parameter for setting ROI in radians (#213) * optical_flow: parameter for setting ROI in radians * Compatibility with old OpenCV --- clover/launch/clover.launch | 1 + clover/src/optical_flow.cpp | 36 ++++++++++++++++++++++++++++++------ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/clover/launch/clover.launch b/clover/launch/clover.launch index e734b2a4..02680f57 100644 --- a/clover/launch/clover.launch +++ b/clover/launch/clover.launch @@ -35,6 +35,7 @@ + diff --git a/clover/src/optical_flow.cpp b/clover/src/optical_flow.cpp index dfe370b3..8f1a3c0f 100644 --- a/clover/src/optical_flow.cpp +++ b/clover/src/optical_flow.cpp @@ -46,7 +46,9 @@ private: image_transport::CameraSubscriber img_sub_; image_transport::Publisher img_pub_; mavros_msgs::OpticalFlowRad flow_; - int roi_, roi_2_; + int roi_px_; + double roi_rad_; + cv::Rect roi_; Mat hann_; Mat prev_, curr_; Mat camera_matrix_, dist_coeffs_; @@ -63,8 +65,8 @@ private: nh.param("mavros/local_position/tf/frame_id", local_frame_id_, "map"); nh.param("mavros/local_position/tf/child_frame_id", fcu_frame_id_, "base_link"); - nh_priv.param("roi", roi_, 128); - roi_2_ = roi_ / 2; + nh_priv.param("roi", roi_px_, 128); + nh_priv.param("roi_rad", roi_rad_, 0.0); nh_priv.param("calc_flow_gyro", calc_flow_gyro_, false); img_sub_ = it.subscribeCamera("image_raw", 1, &OpticalFlow::flow, this); @@ -112,9 +114,31 @@ private: auto img = cv_bridge::toCvShare(msg, "mono8")->image; - // Apply ROI - if (roi_ != 0) { - img = img(cv::Rect((msg->width / 2 - roi_2_), (msg->height / 2 - roi_2_), roi_, roi_)); + if (roi_.width == 0) { // ROI is not calculated + // Calculate ROI + if (roi_rad_ != 0) { + std::vector object_points = { + cv::Point3f(-sin(roi_rad_ / 2), -sin(roi_rad_ / 2), cos(roi_rad_ / 2)), + cv::Point3f(sin(roi_rad_ / 2), sin(roi_rad_ / 2), cos(roi_rad_ / 2)), + }; + + std::vector vec { 0, 0, 0 }; + std::vector img_points; + cv::projectPoints(object_points, vec, vec, camera_matrix_, dist_coeffs_, img_points); + + roi_ = cv::Rect(cv::Point2i(round(img_points[0].x), round(img_points[0].y)), + cv::Point2i(round(img_points[1].x), round(img_points[1].y))); + + ROS_INFO("ROI: %d %d - %d %d ", roi_.tl().x, roi_.tl().y, roi_.br().x, roi_.br().y); + + } else if (roi_px_ != 0) { + roi_ = cv::Rect((msg->width / 2 - roi_px_ / 2), (msg->height / 2 - roi_px_ / 2), roi_px_, roi_px_); + } + } + + if (roi_.width != 0) { // ROI is set + // Apply ROI + img = img(roi_); } img.convertTo(curr_, CV_32F); From d6f8f4017fc826444b05fb0e0668be8d2dde5e3e Mon Sep 17 00:00:00 2001 From: Alexey Rogachevskiy Date: Fri, 14 Feb 2020 19:22:02 +0300 Subject: [PATCH 06/14] clover: Update roslib.js and ros3d.js --- clover/www/js/ros3d.js | 57892 +++----------------------------------- clover/www/js/roslib.js | 1071 +- 2 files changed, 4454 insertions(+), 54509 deletions(-) diff --git a/clover/www/js/ros3d.js b/clover/www/js/ros3d.js index f2db774d..2a607c25 100644 --- a/clover/www/js/ros3d.js +++ b/clover/www/js/ros3d.js @@ -1,45089 +1,52 @@ -var ROS3D = (function (exports,ROSLIB) { -'use strict'; - -// Polyfills - -if ( Number.EPSILON === undefined ) { - - Number.EPSILON = Math.pow( 2, - 52 ); - -} - -if ( Number.isInteger === undefined ) { - - // Missing in IE - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger - - Number.isInteger = function ( value ) { - - return typeof value === 'number' && isFinite( value ) && Math.floor( value ) === value; - - }; - -} - -// - -if ( Math.sign === undefined ) { - - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign - - Math.sign = function ( x ) { - - return ( x < 0 ) ? - 1 : ( x > 0 ) ? 1 : + x; - - }; - -} - -if ( 'name' in Function.prototype === false ) { - - // Missing in IE - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name - - Object.defineProperty( Function.prototype, 'name', { - - get: function () { - - return this.toString().match( /^\s*function\s*([^\(\s]*)/ )[ 1 ]; - - } - - } ); - -} - -if ( Object.assign === undefined ) { - - // Missing in IE - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign - - ( function () { - - Object.assign = function ( target ) { - - if ( target === undefined || target === null ) { - - throw new TypeError( 'Cannot convert undefined or null to object' ); - - } - - var output = Object( target ); - - for ( var index = 1; index < arguments.length; index ++ ) { - - var source = arguments[ index ]; - - if ( source !== undefined && source !== null ) { - - for ( var nextKey in source ) { - - if ( Object.prototype.hasOwnProperty.call( source, nextKey ) ) { - - output[ nextKey ] = source[ nextKey ]; - - } - - } - - } - - } - - return output; - - }; - - } )(); - -} - -/** - * https://github.com/mrdoob/eventdispatcher.js/ - */ - -function EventDispatcher() {} - -Object.assign( EventDispatcher.prototype, { - - addEventListener: function ( type, listener ) { - - if ( this._listeners === undefined ) this._listeners = {}; - - var listeners = this._listeners; - - if ( listeners[ type ] === undefined ) { - - listeners[ type ] = []; - - } - - if ( listeners[ type ].indexOf( listener ) === - 1 ) { - - listeners[ type ].push( listener ); - - } - - }, - - hasEventListener: function ( type, listener ) { - - if ( this._listeners === undefined ) return false; - - var listeners = this._listeners; - - return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1; - - }, - - removeEventListener: function ( type, listener ) { - - if ( this._listeners === undefined ) return; - - var listeners = this._listeners; - var listenerArray = listeners[ type ]; - - if ( listenerArray !== undefined ) { - - var index = listenerArray.indexOf( listener ); - - if ( index !== - 1 ) { - - listenerArray.splice( index, 1 ); - - } - - } - - }, - - dispatchEvent: function ( event ) { - - if ( this._listeners === undefined ) return; - - var listeners = this._listeners; - var listenerArray = listeners[ event.type ]; - - if ( listenerArray !== undefined ) { - - event.target = this; - - var array = listenerArray.slice( 0 ); - - for ( var i = 0, l = array.length; i < l; i ++ ) { - - array[ i ].call( this, event ); - - } - - } - - } - -} ); - -var REVISION = '88'; -var MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2 }; -var CullFaceNone = 0; -var CullFaceBack = 1; -var CullFaceFront = 2; -var CullFaceFrontBack = 3; -var FrontFaceDirectionCW = 0; -var FrontFaceDirectionCCW = 1; -var BasicShadowMap = 0; -var PCFShadowMap = 1; -var PCFSoftShadowMap = 2; -var FrontSide = 0; -var BackSide = 1; -var DoubleSide = 2; -var FlatShading = 1; -var SmoothShading = 2; -var NoColors = 0; -var FaceColors = 1; -var VertexColors = 2; -var NoBlending = 0; -var NormalBlending = 1; -var AdditiveBlending = 2; -var SubtractiveBlending = 3; -var MultiplyBlending = 4; -var CustomBlending = 5; -var AddEquation = 100; -var SubtractEquation = 101; -var ReverseSubtractEquation = 102; -var MinEquation = 103; -var MaxEquation = 104; -var ZeroFactor = 200; -var OneFactor = 201; -var SrcColorFactor = 202; -var OneMinusSrcColorFactor = 203; -var SrcAlphaFactor = 204; -var OneMinusSrcAlphaFactor = 205; -var DstAlphaFactor = 206; -var OneMinusDstAlphaFactor = 207; -var DstColorFactor = 208; -var OneMinusDstColorFactor = 209; -var SrcAlphaSaturateFactor = 210; -var NeverDepth = 0; -var AlwaysDepth = 1; -var LessDepth = 2; -var LessEqualDepth = 3; -var EqualDepth = 4; -var GreaterEqualDepth = 5; -var GreaterDepth = 6; -var NotEqualDepth = 7; -var MultiplyOperation = 0; -var MixOperation = 1; -var AddOperation = 2; -var NoToneMapping = 0; -var LinearToneMapping = 1; -var ReinhardToneMapping = 2; -var Uncharted2ToneMapping = 3; -var CineonToneMapping = 4; -var UVMapping = 300; -var CubeReflectionMapping = 301; -var CubeRefractionMapping = 302; -var EquirectangularReflectionMapping = 303; -var EquirectangularRefractionMapping = 304; -var SphericalReflectionMapping = 305; -var CubeUVReflectionMapping = 306; -var CubeUVRefractionMapping = 307; -var RepeatWrapping = 1000; -var ClampToEdgeWrapping = 1001; -var MirroredRepeatWrapping = 1002; -var NearestFilter = 1003; -var NearestMipMapNearestFilter = 1004; -var NearestMipMapLinearFilter = 1005; -var LinearFilter = 1006; -var LinearMipMapNearestFilter = 1007; -var LinearMipMapLinearFilter = 1008; -var UnsignedByteType = 1009; -var ByteType = 1010; -var ShortType = 1011; -var UnsignedShortType = 1012; -var IntType = 1013; -var UnsignedIntType = 1014; -var FloatType = 1015; -var HalfFloatType = 1016; -var UnsignedShort4444Type = 1017; -var UnsignedShort5551Type = 1018; -var UnsignedShort565Type = 1019; -var UnsignedInt248Type = 1020; -var AlphaFormat = 1021; -var RGBFormat = 1022; -var RGBAFormat = 1023; -var LuminanceFormat = 1024; -var LuminanceAlphaFormat = 1025; -var RGBEFormat = RGBAFormat; -var DepthFormat = 1026; -var DepthStencilFormat = 1027; -var RGB_S3TC_DXT1_Format = 2001; -var RGBA_S3TC_DXT1_Format = 2002; -var RGBA_S3TC_DXT3_Format = 2003; -var RGBA_S3TC_DXT5_Format = 2004; -var RGB_PVRTC_4BPPV1_Format = 2100; -var RGB_PVRTC_2BPPV1_Format = 2101; -var RGBA_PVRTC_4BPPV1_Format = 2102; -var RGBA_PVRTC_2BPPV1_Format = 2103; -var RGB_ETC1_Format = 2151; -var LoopOnce = 2200; -var LoopRepeat = 2201; -var LoopPingPong = 2202; -var InterpolateDiscrete = 2300; -var InterpolateLinear = 2301; -var InterpolateSmooth = 2302; -var ZeroCurvatureEnding = 2400; -var ZeroSlopeEnding = 2401; -var WrapAroundEnding = 2402; -var TrianglesDrawMode = 0; -var TriangleStripDrawMode = 1; -var TriangleFanDrawMode = 2; -var LinearEncoding = 3000; -var sRGBEncoding = 3001; -var GammaEncoding = 3007; -var RGBEEncoding = 3002; -var LogLuvEncoding = 3003; -var RGBM7Encoding = 3004; -var RGBM16Encoding = 3005; -var RGBDEncoding = 3006; -var BasicDepthPacking = 3200; -var RGBADepthPacking = 3201; - -/** - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - */ - -var _Math = { - - DEG2RAD: Math.PI / 180, - RAD2DEG: 180 / Math.PI, - - generateUUID: function () { - - // http://www.broofa.com/Tools/Math.uuid.htm - // Replaced .join with string concatenation (@takahirox) - - var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split( '' ); - var rnd = 0, r; - - return function generateUUID() { - - var uuid = ''; - - for ( var i = 0; i < 36; i ++ ) { - - if ( i === 8 || i === 13 || i === 18 || i === 23 ) { - - uuid += '-'; - - } else if ( i === 14 ) { - - uuid += '4'; - - } else { - - if ( rnd <= 0x02 ) rnd = 0x2000000 + ( Math.random() * 0x1000000 ) | 0; - r = rnd & 0xf; - rnd = rnd >> 4; - uuid += chars[ ( i === 19 ) ? ( r & 0x3 ) | 0x8 : r ]; - - } - - } - - return uuid; - - }; - - }(), - - clamp: function ( value, min, max ) { - - return Math.max( min, Math.min( max, value ) ); - - }, - - // compute euclidian modulo of m % n - // https://en.wikipedia.org/wiki/Modulo_operation - - euclideanModulo: function ( n, m ) { - - return ( ( n % m ) + m ) % m; - - }, - - // Linear mapping from range to range - - mapLinear: function ( x, a1, a2, b1, b2 ) { - - return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); - - }, - - // https://en.wikipedia.org/wiki/Linear_interpolation - - lerp: function ( x, y, t ) { - - return ( 1 - t ) * x + t * y; - - }, - - // http://en.wikipedia.org/wiki/Smoothstep - - smoothstep: function ( x, min, max ) { - - if ( x <= min ) return 0; - if ( x >= max ) return 1; - - x = ( x - min ) / ( max - min ); - - return x * x * ( 3 - 2 * x ); - - }, - - smootherstep: function ( x, min, max ) { - - if ( x <= min ) return 0; - if ( x >= max ) return 1; - - x = ( x - min ) / ( max - min ); - - return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); - - }, - - // Random integer from interval - - randInt: function ( low, high ) { - - return low + Math.floor( Math.random() * ( high - low + 1 ) ); - - }, - - // Random float from interval - - randFloat: function ( low, high ) { - - return low + Math.random() * ( high - low ); - - }, - - // Random float from <-range/2, range/2> interval - - randFloatSpread: function ( range ) { - - return range * ( 0.5 - Math.random() ); - - }, - - degToRad: function ( degrees ) { - - return degrees * _Math.DEG2RAD; - - }, - - radToDeg: function ( radians ) { - - return radians * _Math.RAD2DEG; - - }, - - isPowerOfTwo: function ( value ) { - - return ( value & ( value - 1 ) ) === 0 && value !== 0; - - }, - - ceilPowerOfTwo: function ( value ) { - - return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); - - }, - - floorPowerOfTwo: function ( value ) { - - return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); - - } - -}; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author philogb / http://blog.thejit.org/ - * @author egraether / http://egraether.com/ - * @author zz85 / http://www.lab4games.net/zz85/blog - */ - -function Vector2( x, y ) { - - this.x = x || 0; - this.y = y || 0; - -} - -Object.defineProperties( Vector2.prototype, { - - "width": { - - get: function () { - - return this.x; - - }, - - set: function ( value ) { - - this.x = value; - - } - - }, - - "height": { - - get: function () { - - return this.y; - - }, - - set: function ( value ) { - - this.y = value; - - } - - } - -} ); - -Object.assign( Vector2.prototype, { - - isVector2: true, - - set: function ( x, y ) { - - this.x = x; - this.y = y; - - return this; - - }, - - setScalar: function ( scalar ) { - - this.x = scalar; - this.y = scalar; - - return this; - - }, - - setX: function ( x ) { - - this.x = x; - - return this; - - }, - - setY: function ( y ) { - - this.y = y; - - return this; - - }, - - setComponent: function ( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - default: throw new Error( 'index is out of range: ' + index ); - - } - - return this; - - }, - - getComponent: function ( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - default: throw new Error( 'index is out of range: ' + index ); - - } - - }, - - clone: function () { - - return new this.constructor( this.x, this.y ); - - }, - - copy: function ( v ) { - - this.x = v.x; - this.y = v.y; - - return this; - - }, - - add: function ( v, w ) { - - if ( w !== undefined ) { - - console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); - return this.addVectors( v, w ); - - } - - this.x += v.x; - this.y += v.y; - - return this; - - }, - - addScalar: function ( s ) { - - this.x += s; - this.y += s; - - return this; - - }, - - addVectors: function ( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - - return this; - - }, - - addScaledVector: function ( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - - return this; - - }, - - sub: function ( v, w ) { - - if ( w !== undefined ) { - - console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); - return this.subVectors( v, w ); - - } - - this.x -= v.x; - this.y -= v.y; - - return this; - - }, - - subScalar: function ( s ) { - - this.x -= s; - this.y -= s; - - return this; - - }, - - subVectors: function ( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - - return this; - - }, - - multiply: function ( v ) { - - this.x *= v.x; - this.y *= v.y; - - return this; - - }, - - multiplyScalar: function ( scalar ) { - - this.x *= scalar; - this.y *= scalar; - - return this; - - }, - - divide: function ( v ) { - - this.x /= v.x; - this.y /= v.y; - - return this; - - }, - - divideScalar: function ( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - }, - - applyMatrix3: function ( m ) { - - var x = this.x, y = this.y; - var e = m.elements; - - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; - - return this; - - }, - - min: function ( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - - return this; - - }, - - max: function ( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - - return this; - - }, - - clamp: function ( min, max ) { - - // assumes min < max, componentwise - - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - - return this; - - }, - - clampScalar: function () { - - var min = new Vector2(); - var max = new Vector2(); - - return function clampScalar( minVal, maxVal ) { - - min.set( minVal, minVal ); - max.set( maxVal, maxVal ); - - return this.clamp( min, max ); - - }; - - }(), - - clampLength: function ( min, max ) { - - var length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - - }, - - floor: function () { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - - return this; - - }, - - ceil: function () { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - - return this; - - }, - - round: function () { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - - return this; - - }, - - roundToZero: function () { - - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - - return this; - - }, - - negate: function () { - - this.x = - this.x; - this.y = - this.y; - - return this; - - }, - - dot: function ( v ) { - - return this.x * v.x + this.y * v.y; - - }, - - lengthSq: function () { - - return this.x * this.x + this.y * this.y; - - }, - - length: function () { - - return Math.sqrt( this.x * this.x + this.y * this.y ); - - }, - - manhattanLength: function () { - - return Math.abs( this.x ) + Math.abs( this.y ); - - }, - - normalize: function () { - - return this.divideScalar( this.length() || 1 ); - - }, - - angle: function () { - - // computes the angle in radians with respect to the positive x-axis - - var angle = Math.atan2( this.y, this.x ); - - if ( angle < 0 ) angle += 2 * Math.PI; - - return angle; - - }, - - distanceTo: function ( v ) { - - return Math.sqrt( this.distanceToSquared( v ) ); - - }, - - distanceToSquared: function ( v ) { - - var dx = this.x - v.x, dy = this.y - v.y; - return dx * dx + dy * dy; - - }, - - manhattanDistanceTo: function ( v ) { - - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); - - }, - - setLength: function ( length ) { - - return this.normalize().multiplyScalar( length ); - - }, - - lerp: function ( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - - return this; - - }, - - lerpVectors: function ( v1, v2, alpha ) { - - return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 ); - - }, - - equals: function ( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) ); - - }, - - fromArray: function ( array, offset ) { - - if ( offset === undefined ) offset = 0; - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - - return this; - - }, - - toArray: function ( array, offset ) { - - if ( array === undefined ) array = []; - if ( offset === undefined ) offset = 0; - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - - return array; - - }, - - fromBufferAttribute: function ( attribute, index, offset ) { - - if ( offset !== undefined ) { - - console.warn( 'THREE.Vector2: offset has been removed from .fromBufferAttribute().' ); - - } - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - - return this; - - }, - - rotateAround: function ( center, angle ) { - - var c = Math.cos( angle ), s = Math.sin( angle ); - - var x = this.x - center.x; - var y = this.y - center.y; - - this.x = x * c - y * s + center.x; - this.y = x * s + y * c + center.y; - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author supereggbert / http://www.paulbrunt.co.uk/ - * @author philogb / http://blog.thejit.org/ - * @author jordi_ros / http://plattsoft.com - * @author D1plo1d / http://github.com/D1plo1d - * @author alteredq / http://alteredqualia.com/ - * @author mikael emtinger / http://gomo.se/ - * @author timknip / http://www.floorplanner.com/ - * @author bhouston / http://clara.io - * @author WestLangley / http://github.com/WestLangley - */ - -function Matrix4() { - - this.elements = [ - - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ]; - - if ( arguments.length > 0 ) { - - console.error( 'THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.' ); - - } - -} - -Object.assign( Matrix4.prototype, { - - isMatrix4: true, - - set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { - - var te = this.elements; - - te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14; - te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24; - te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34; - te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44; - - return this; - - }, - - identity: function () { - - this.set( - - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - }, - - clone: function () { - - return new Matrix4().fromArray( this.elements ); - - }, - - copy: function ( m ) { - - var te = this.elements; - var me = m.elements; - - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ]; - te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; - te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ]; - te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ]; - - return this; - - }, - - copyPosition: function ( m ) { - - var te = this.elements, me = m.elements; - - te[ 12 ] = me[ 12 ]; - te[ 13 ] = me[ 13 ]; - te[ 14 ] = me[ 14 ]; - - return this; - - }, - - extractBasis: function ( xAxis, yAxis, zAxis ) { - - xAxis.setFromMatrixColumn( this, 0 ); - yAxis.setFromMatrixColumn( this, 1 ); - zAxis.setFromMatrixColumn( this, 2 ); - - return this; - - }, - - makeBasis: function ( xAxis, yAxis, zAxis ) { - - this.set( - xAxis.x, yAxis.x, zAxis.x, 0, - xAxis.y, yAxis.y, zAxis.y, 0, - xAxis.z, yAxis.z, zAxis.z, 0, - 0, 0, 0, 1 - ); - - return this; - - }, - - extractRotation: function () { - - var v1 = new Vector3(); - - return function extractRotation( m ) { - - var te = this.elements; - var me = m.elements; - - var scaleX = 1 / v1.setFromMatrixColumn( m, 0 ).length(); - var scaleY = 1 / v1.setFromMatrixColumn( m, 1 ).length(); - var scaleZ = 1 / v1.setFromMatrixColumn( m, 2 ).length(); - - te[ 0 ] = me[ 0 ] * scaleX; - te[ 1 ] = me[ 1 ] * scaleX; - te[ 2 ] = me[ 2 ] * scaleX; - - te[ 4 ] = me[ 4 ] * scaleY; - te[ 5 ] = me[ 5 ] * scaleY; - te[ 6 ] = me[ 6 ] * scaleY; - - te[ 8 ] = me[ 8 ] * scaleZ; - te[ 9 ] = me[ 9 ] * scaleZ; - te[ 10 ] = me[ 10 ] * scaleZ; - - return this; - - }; - - }(), - - makeRotationFromEuler: function ( euler ) { - - if ( ! ( euler && euler.isEuler ) ) { - - console.error( 'THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' ); - - } - - var te = this.elements; - - var x = euler.x, y = euler.y, z = euler.z; - var a = Math.cos( x ), b = Math.sin( x ); - var c = Math.cos( y ), d = Math.sin( y ); - var e = Math.cos( z ), f = Math.sin( z ); - - if ( euler.order === 'XYZ' ) { - - var ae = a * e, af = a * f, be = b * e, bf = b * f; - - te[ 0 ] = c * e; - te[ 4 ] = - c * f; - te[ 8 ] = d; - - te[ 1 ] = af + be * d; - te[ 5 ] = ae - bf * d; - te[ 9 ] = - b * c; - - te[ 2 ] = bf - ae * d; - te[ 6 ] = be + af * d; - te[ 10 ] = a * c; - - } else if ( euler.order === 'YXZ' ) { - - var ce = c * e, cf = c * f, de = d * e, df = d * f; - - te[ 0 ] = ce + df * b; - te[ 4 ] = de * b - cf; - te[ 8 ] = a * d; - - te[ 1 ] = a * f; - te[ 5 ] = a * e; - te[ 9 ] = - b; - - te[ 2 ] = cf * b - de; - te[ 6 ] = df + ce * b; - te[ 10 ] = a * c; - - } else if ( euler.order === 'ZXY' ) { - - var ce = c * e, cf = c * f, de = d * e, df = d * f; - - te[ 0 ] = ce - df * b; - te[ 4 ] = - a * f; - te[ 8 ] = de + cf * b; - - te[ 1 ] = cf + de * b; - te[ 5 ] = a * e; - te[ 9 ] = df - ce * b; - - te[ 2 ] = - a * d; - te[ 6 ] = b; - te[ 10 ] = a * c; - - } else if ( euler.order === 'ZYX' ) { - - var ae = a * e, af = a * f, be = b * e, bf = b * f; - - te[ 0 ] = c * e; - te[ 4 ] = be * d - af; - te[ 8 ] = ae * d + bf; - - te[ 1 ] = c * f; - te[ 5 ] = bf * d + ae; - te[ 9 ] = af * d - be; - - te[ 2 ] = - d; - te[ 6 ] = b * c; - te[ 10 ] = a * c; - - } else if ( euler.order === 'YZX' ) { - - var ac = a * c, ad = a * d, bc = b * c, bd = b * d; - - te[ 0 ] = c * e; - te[ 4 ] = bd - ac * f; - te[ 8 ] = bc * f + ad; - - te[ 1 ] = f; - te[ 5 ] = a * e; - te[ 9 ] = - b * e; - - te[ 2 ] = - d * e; - te[ 6 ] = ad * f + bc; - te[ 10 ] = ac - bd * f; - - } else if ( euler.order === 'XZY' ) { - - var ac = a * c, ad = a * d, bc = b * c, bd = b * d; - - te[ 0 ] = c * e; - te[ 4 ] = - f; - te[ 8 ] = d * e; - - te[ 1 ] = ac * f + bd; - te[ 5 ] = a * e; - te[ 9 ] = ad * f - bc; - - te[ 2 ] = bc * f - ad; - te[ 6 ] = b * e; - te[ 10 ] = bd * f + ac; - - } - - // last column - te[ 3 ] = 0; - te[ 7 ] = 0; - te[ 11 ] = 0; - - // bottom row - te[ 12 ] = 0; - te[ 13 ] = 0; - te[ 14 ] = 0; - te[ 15 ] = 1; - - return this; - - }, - - makeRotationFromQuaternion: function ( q ) { - - var te = this.elements; - - var x = q._x, y = q._y, z = q._z, w = q._w; - var x2 = x + x, y2 = y + y, z2 = z + z; - var xx = x * x2, xy = x * y2, xz = x * z2; - var yy = y * y2, yz = y * z2, zz = z * z2; - var wx = w * x2, wy = w * y2, wz = w * z2; - - te[ 0 ] = 1 - ( yy + zz ); - te[ 4 ] = xy - wz; - te[ 8 ] = xz + wy; - - te[ 1 ] = xy + wz; - te[ 5 ] = 1 - ( xx + zz ); - te[ 9 ] = yz - wx; - - te[ 2 ] = xz - wy; - te[ 6 ] = yz + wx; - te[ 10 ] = 1 - ( xx + yy ); - - // last column - te[ 3 ] = 0; - te[ 7 ] = 0; - te[ 11 ] = 0; - - // bottom row - te[ 12 ] = 0; - te[ 13 ] = 0; - te[ 14 ] = 0; - te[ 15 ] = 1; - - return this; - - }, - - lookAt: function () { - - var x = new Vector3(); - var y = new Vector3(); - var z = new Vector3(); - - return function lookAt( eye, target, up ) { - - var te = this.elements; - - z.subVectors( eye, target ); - - if ( z.lengthSq() === 0 ) { - - // eye and target are in the same position - - z.z = 1; - - } - - z.normalize(); - x.crossVectors( up, z ); - - if ( x.lengthSq() === 0 ) { - - // up and z are parallel - - if ( Math.abs( up.z ) === 1 ) { - - z.x += 0.0001; - - } else { - - z.z += 0.0001; - - } - - z.normalize(); - x.crossVectors( up, z ); - - } - - x.normalize(); - y.crossVectors( z, x ); - - te[ 0 ] = x.x; te[ 4 ] = y.x; te[ 8 ] = z.x; - te[ 1 ] = x.y; te[ 5 ] = y.y; te[ 9 ] = z.y; - te[ 2 ] = x.z; te[ 6 ] = y.z; te[ 10 ] = z.z; - - return this; - - }; - - }(), - - multiply: function ( m, n ) { - - if ( n !== undefined ) { - - console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' ); - return this.multiplyMatrices( m, n ); - - } - - return this.multiplyMatrices( this, m ); - - }, - - premultiply: function ( m ) { - - return this.multiplyMatrices( m, this ); - - }, - - multiplyMatrices: function ( a, b ) { - - var ae = a.elements; - var be = b.elements; - var te = this.elements; - - var a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ]; - var a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ]; - var a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ]; - var a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ]; - - var b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ]; - var b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ]; - var b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ]; - var b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ]; - - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; - te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; - te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; - te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; - - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; - te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; - te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; - te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; - - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; - te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; - te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; - te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; - - te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; - te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; - te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; - te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; - - return this; - - }, - - multiplyScalar: function ( s ) { - - var te = this.elements; - - te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s; - te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s; - te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s; - te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s; - - return this; - - }, - - applyToBufferAttribute: function () { - - var v1 = new Vector3(); - - return function applyToBufferAttribute( attribute ) { - - for ( var i = 0, l = attribute.count; i < l; i ++ ) { - - v1.x = attribute.getX( i ); - v1.y = attribute.getY( i ); - v1.z = attribute.getZ( i ); - - v1.applyMatrix4( this ); - - attribute.setXYZ( i, v1.x, v1.y, v1.z ); - - } - - return attribute; - - }; - - }(), - - determinant: function () { - - var te = this.elements; - - var n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ]; - var n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ]; - var n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ]; - var n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; - - //TODO: make this more efficient - //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm ) - - return ( - n41 * ( - + n14 * n23 * n32 - - n13 * n24 * n32 - - n14 * n22 * n33 - + n12 * n24 * n33 - + n13 * n22 * n34 - - n12 * n23 * n34 - ) + - n42 * ( - + n11 * n23 * n34 - - n11 * n24 * n33 - + n14 * n21 * n33 - - n13 * n21 * n34 - + n13 * n24 * n31 - - n14 * n23 * n31 - ) + - n43 * ( - + n11 * n24 * n32 - - n11 * n22 * n34 - - n14 * n21 * n32 - + n12 * n21 * n34 - + n14 * n22 * n31 - - n12 * n24 * n31 - ) + - n44 * ( - - n13 * n22 * n31 - - n11 * n23 * n32 - + n11 * n22 * n33 - + n13 * n21 * n32 - - n12 * n21 * n33 - + n12 * n23 * n31 - ) - - ); - - }, - - transpose: function () { - - var te = this.elements; - var tmp; - - tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp; - tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp; - tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp; - - tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp; - tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp; - tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp; - - return this; - - }, - - setPosition: function ( v ) { - - var te = this.elements; - - te[ 12 ] = v.x; - te[ 13 ] = v.y; - te[ 14 ] = v.z; - - return this; - - }, - - getInverse: function ( m, throwOnDegenerate ) { - - // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm - var te = this.elements, - me = m.elements, - - n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ], n41 = me[ 3 ], - n12 = me[ 4 ], n22 = me[ 5 ], n32 = me[ 6 ], n42 = me[ 7 ], - n13 = me[ 8 ], n23 = me[ 9 ], n33 = me[ 10 ], n43 = me[ 11 ], - n14 = me[ 12 ], n24 = me[ 13 ], n34 = me[ 14 ], n44 = me[ 15 ], - - t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44, - t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44, - t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44, - t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; - - var det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14; - - if ( det === 0 ) { - - var msg = "THREE.Matrix4: .getInverse() can't invert matrix, determinant is 0"; - - if ( throwOnDegenerate === true ) { - - throw new Error( msg ); - - } else { - - console.warn( msg ); - - } - - return this.identity(); - - } - - var detInv = 1 / det; - - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv; - te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv; - te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv; - - te[ 4 ] = t12 * detInv; - te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv; - te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv; - te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv; - - te[ 8 ] = t13 * detInv; - te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv; - te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv; - te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv; - - te[ 12 ] = t14 * detInv; - te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv; - te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv; - te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv; - - return this; - - }, - - scale: function ( v ) { - - var te = this.elements; - var x = v.x, y = v.y, z = v.z; - - te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z; - te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z; - te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z; - te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z; - - return this; - - }, - - getMaxScaleOnAxis: function () { - - var te = this.elements; - - var scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ]; - var scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ]; - var scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ]; - - return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) ); - - }, - - makeTranslation: function ( x, y, z ) { - - this.set( - - 1, 0, 0, x, - 0, 1, 0, y, - 0, 0, 1, z, - 0, 0, 0, 1 - - ); - - return this; - - }, - - makeRotationX: function ( theta ) { - - var c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - 1, 0, 0, 0, - 0, c, - s, 0, - 0, s, c, 0, - 0, 0, 0, 1 - - ); - - return this; - - }, - - makeRotationY: function ( theta ) { - - var c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - c, 0, s, 0, - 0, 1, 0, 0, - - s, 0, c, 0, - 0, 0, 0, 1 - - ); - - return this; - - }, - - makeRotationZ: function ( theta ) { - - var c = Math.cos( theta ), s = Math.sin( theta ); - - this.set( - - c, - s, 0, 0, - s, c, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - }, - - makeRotationAxis: function ( axis, angle ) { - - // Based on http://www.gamedev.net/reference/articles/article1199.asp - - var c = Math.cos( angle ); - var s = Math.sin( angle ); - var t = 1 - c; - var x = axis.x, y = axis.y, z = axis.z; - var tx = t * x, ty = t * y; - - this.set( - - tx * x + c, tx * y - s * z, tx * z + s * y, 0, - tx * y + s * z, ty * y + c, ty * z - s * x, 0, - tx * z - s * y, ty * z + s * x, t * z * z + c, 0, - 0, 0, 0, 1 - - ); - - return this; - - }, - - makeScale: function ( x, y, z ) { - - this.set( - - x, 0, 0, 0, - 0, y, 0, 0, - 0, 0, z, 0, - 0, 0, 0, 1 - - ); - - return this; - - }, - - makeShear: function ( x, y, z ) { - - this.set( - - 1, y, z, 0, - x, 1, z, 0, - x, y, 1, 0, - 0, 0, 0, 1 - - ); - - return this; - - }, - - compose: function ( position, quaternion, scale ) { - - this.makeRotationFromQuaternion( quaternion ); - this.scale( scale ); - this.setPosition( position ); - - return this; - - }, - - decompose: function () { - - var vector = new Vector3(); - var matrix = new Matrix4(); - - return function decompose( position, quaternion, scale ) { - - var te = this.elements; - - var sx = vector.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length(); - var sy = vector.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length(); - var sz = vector.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length(); - - // if determine is negative, we need to invert one scale - var det = this.determinant(); - if ( det < 0 ) sx = - sx; - - position.x = te[ 12 ]; - position.y = te[ 13 ]; - position.z = te[ 14 ]; - - // scale the rotation part - matrix.copy( this ); - - var invSX = 1 / sx; - var invSY = 1 / sy; - var invSZ = 1 / sz; - - matrix.elements[ 0 ] *= invSX; - matrix.elements[ 1 ] *= invSX; - matrix.elements[ 2 ] *= invSX; - - matrix.elements[ 4 ] *= invSY; - matrix.elements[ 5 ] *= invSY; - matrix.elements[ 6 ] *= invSY; - - matrix.elements[ 8 ] *= invSZ; - matrix.elements[ 9 ] *= invSZ; - matrix.elements[ 10 ] *= invSZ; - - quaternion.setFromRotationMatrix( matrix ); - - scale.x = sx; - scale.y = sy; - scale.z = sz; - - return this; - - }; - - }(), - - makePerspective: function ( left, right, top, bottom, near, far ) { - - if ( far === undefined ) { - - console.warn( 'THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.' ); - - } - - var te = this.elements; - var x = 2 * near / ( right - left ); - var y = 2 * near / ( top - bottom ); - - var a = ( right + left ) / ( right - left ); - var b = ( top + bottom ) / ( top - bottom ); - var c = - ( far + near ) / ( far - near ); - var d = - 2 * far * near / ( far - near ); - - te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; - te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0; - - return this; - - }, - - makeOrthographic: function ( left, right, top, bottom, near, far ) { - - var te = this.elements; - var w = 1.0 / ( right - left ); - var h = 1.0 / ( top - bottom ); - var p = 1.0 / ( far - near ); - - var x = ( right + left ) * w; - var y = ( top + bottom ) * h; - var z = ( far + near ) * p; - - te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; - te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; - te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 2 * p; te[ 14 ] = - z; - te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; - - return this; - - }, - - equals: function ( matrix ) { - - var te = this.elements; - var me = matrix.elements; - - for ( var i = 0; i < 16; i ++ ) { - - if ( te[ i ] !== me[ i ] ) return false; - - } - - return true; - - }, - - fromArray: function ( array, offset ) { - - if ( offset === undefined ) offset = 0; - - for ( var i = 0; i < 16; i ++ ) { - - this.elements[ i ] = array[ i + offset ]; - - } - - return this; - - }, - - toArray: function ( array, offset ) { - - if ( array === undefined ) array = []; - if ( offset === undefined ) offset = 0; - - var te = this.elements; - - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; - array[ offset + 3 ] = te[ 3 ]; - - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - - array[ offset + 8 ] = te[ 8 ]; - array[ offset + 9 ] = te[ 9 ]; - array[ offset + 10 ] = te[ 10 ]; - array[ offset + 11 ] = te[ 11 ]; - - array[ offset + 12 ] = te[ 12 ]; - array[ offset + 13 ] = te[ 13 ]; - array[ offset + 14 ] = te[ 14 ]; - array[ offset + 15 ] = te[ 15 ]; - - return array; - - } - -} ); - -/** - * @author mikael emtinger / http://gomo.se/ - * @author alteredq / http://alteredqualia.com/ - * @author WestLangley / http://github.com/WestLangley - * @author bhouston / http://clara.io - */ - -function Quaternion( x, y, z, w ) { - - this._x = x || 0; - this._y = y || 0; - this._z = z || 0; - this._w = ( w !== undefined ) ? w : 1; - -} - -Object.assign( Quaternion, { - - slerp: function ( qa, qb, qm, t ) { - - return qm.copy( qa ).slerp( qb, t ); - - }, - - slerpFlat: function ( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { - - // fuzz-free, array-based Quaternion SLERP operation - - var x0 = src0[ srcOffset0 + 0 ], - y0 = src0[ srcOffset0 + 1 ], - z0 = src0[ srcOffset0 + 2 ], - w0 = src0[ srcOffset0 + 3 ], - - x1 = src1[ srcOffset1 + 0 ], - y1 = src1[ srcOffset1 + 1 ], - z1 = src1[ srcOffset1 + 2 ], - w1 = src1[ srcOffset1 + 3 ]; - - if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { - - var s = 1 - t, - - cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, - - dir = ( cos >= 0 ? 1 : - 1 ), - sqrSin = 1 - cos * cos; - - // Skip the Slerp for tiny steps to avoid numeric problems: - if ( sqrSin > Number.EPSILON ) { - - var sin = Math.sqrt( sqrSin ), - len = Math.atan2( sin, cos * dir ); - - s = Math.sin( s * len ) / sin; - t = Math.sin( t * len ) / sin; - - } - - var tDir = t * dir; - - x0 = x0 * s + x1 * tDir; - y0 = y0 * s + y1 * tDir; - z0 = z0 * s + z1 * tDir; - w0 = w0 * s + w1 * tDir; - - // Normalize in case we just did a lerp: - if ( s === 1 - t ) { - - var f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); - - x0 *= f; - y0 *= f; - z0 *= f; - w0 *= f; - - } - - } - - dst[ dstOffset ] = x0; - dst[ dstOffset + 1 ] = y0; - dst[ dstOffset + 2 ] = z0; - dst[ dstOffset + 3 ] = w0; - - } - -} ); - -Object.defineProperties( Quaternion.prototype, { - - x: { - - get: function () { - - return this._x; - - }, - - set: function ( value ) { - - this._x = value; - this.onChangeCallback(); - - } - - }, - - y: { - - get: function () { - - return this._y; - - }, - - set: function ( value ) { - - this._y = value; - this.onChangeCallback(); - - } - - }, - - z: { - - get: function () { - - return this._z; - - }, - - set: function ( value ) { - - this._z = value; - this.onChangeCallback(); - - } - - }, - - w: { - - get: function () { - - return this._w; - - }, - - set: function ( value ) { - - this._w = value; - this.onChangeCallback(); - - } - - } - -} ); - -Object.assign( Quaternion.prototype, { - - set: function ( x, y, z, w ) { - - this._x = x; - this._y = y; - this._z = z; - this._w = w; - - this.onChangeCallback(); - - return this; - - }, - - clone: function () { - - return new this.constructor( this._x, this._y, this._z, this._w ); - - }, - - copy: function ( quaternion ) { - - this._x = quaternion.x; - this._y = quaternion.y; - this._z = quaternion.z; - this._w = quaternion.w; - - this.onChangeCallback(); - - return this; - - }, - - setFromEuler: function ( euler, update ) { - - if ( ! ( euler && euler.isEuler ) ) { - - throw new Error( 'THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.' ); - - } - - var x = euler._x, y = euler._y, z = euler._z, order = euler.order; - - // http://www.mathworks.com/matlabcentral/fileexchange/ - // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ - // content/SpinCalc.m - - var cos = Math.cos; - var sin = Math.sin; - - var c1 = cos( x / 2 ); - var c2 = cos( y / 2 ); - var c3 = cos( z / 2 ); - - var s1 = sin( x / 2 ); - var s2 = sin( y / 2 ); - var s3 = sin( z / 2 ); - - if ( order === 'XYZ' ) { - - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - - } else if ( order === 'YXZ' ) { - - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - - } else if ( order === 'ZXY' ) { - - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - - } else if ( order === 'ZYX' ) { - - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - - } else if ( order === 'YZX' ) { - - this._x = s1 * c2 * c3 + c1 * s2 * s3; - this._y = c1 * s2 * c3 + s1 * c2 * s3; - this._z = c1 * c2 * s3 - s1 * s2 * c3; - this._w = c1 * c2 * c3 - s1 * s2 * s3; - - } else if ( order === 'XZY' ) { - - this._x = s1 * c2 * c3 - c1 * s2 * s3; - this._y = c1 * s2 * c3 - s1 * c2 * s3; - this._z = c1 * c2 * s3 + s1 * s2 * c3; - this._w = c1 * c2 * c3 + s1 * s2 * s3; - - } - - if ( update !== false ) this.onChangeCallback(); - - return this; - - }, - - setFromAxisAngle: function ( axis, angle ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm - - // assumes axis is normalized - - var halfAngle = angle / 2, s = Math.sin( halfAngle ); - - this._x = axis.x * s; - this._y = axis.y * s; - this._z = axis.z * s; - this._w = Math.cos( halfAngle ); - - this.onChangeCallback(); - - return this; - - }, - - setFromRotationMatrix: function ( m ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - var te = m.elements, - - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], - - trace = m11 + m22 + m33, - s; - - if ( trace > 0 ) { - - s = 0.5 / Math.sqrt( trace + 1.0 ); - - this._w = 0.25 / s; - this._x = ( m32 - m23 ) * s; - this._y = ( m13 - m31 ) * s; - this._z = ( m21 - m12 ) * s; - - } else if ( m11 > m22 && m11 > m33 ) { - - s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); - - this._w = ( m32 - m23 ) / s; - this._x = 0.25 * s; - this._y = ( m12 + m21 ) / s; - this._z = ( m13 + m31 ) / s; - - } else if ( m22 > m33 ) { - - s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); - - this._w = ( m13 - m31 ) / s; - this._x = ( m12 + m21 ) / s; - this._y = 0.25 * s; - this._z = ( m23 + m32 ) / s; - - } else { - - s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); - - this._w = ( m21 - m12 ) / s; - this._x = ( m13 + m31 ) / s; - this._y = ( m23 + m32 ) / s; - this._z = 0.25 * s; - - } - - this.onChangeCallback(); - - return this; - - }, - - setFromUnitVectors: function () { - - // assumes direction vectors vFrom and vTo are normalized - - var v1 = new Vector3(); - var r; - - var EPS = 0.000001; - - return function setFromUnitVectors( vFrom, vTo ) { - - if ( v1 === undefined ) v1 = new Vector3(); - - r = vFrom.dot( vTo ) + 1; - - if ( r < EPS ) { - - r = 0; - - if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { - - v1.set( - vFrom.y, vFrom.x, 0 ); - - } else { - - v1.set( 0, - vFrom.z, vFrom.y ); - - } - - } else { - - v1.crossVectors( vFrom, vTo ); - - } - - this._x = v1.x; - this._y = v1.y; - this._z = v1.z; - this._w = r; - - return this.normalize(); - - }; - - }(), - - inverse: function () { - - return this.conjugate().normalize(); - - }, - - conjugate: function () { - - this._x *= - 1; - this._y *= - 1; - this._z *= - 1; - - this.onChangeCallback(); - - return this; - - }, - - dot: function ( v ) { - - return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; - - }, - - lengthSq: function () { - - return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; - - }, - - length: function () { - - return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); - - }, - - normalize: function () { - - var l = this.length(); - - if ( l === 0 ) { - - this._x = 0; - this._y = 0; - this._z = 0; - this._w = 1; - - } else { - - l = 1 / l; - - this._x = this._x * l; - this._y = this._y * l; - this._z = this._z * l; - this._w = this._w * l; - - } - - this.onChangeCallback(); - - return this; - - }, - - multiply: function ( q, p ) { - - if ( p !== undefined ) { - - console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' ); - return this.multiplyQuaternions( q, p ); - - } - - return this.multiplyQuaternions( this, q ); - - }, - - premultiply: function ( q ) { - - return this.multiplyQuaternions( q, this ); - - }, - - multiplyQuaternions: function ( a, b ) { - - // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm - - var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; - var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; - - this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; - this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; - this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; - this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; - - this.onChangeCallback(); - - return this; - - }, - - slerp: function ( qb, t ) { - - if ( t === 0 ) return this; - if ( t === 1 ) return this.copy( qb ); - - var x = this._x, y = this._y, z = this._z, w = this._w; - - // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ - - var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; - - if ( cosHalfTheta < 0 ) { - - this._w = - qb._w; - this._x = - qb._x; - this._y = - qb._y; - this._z = - qb._z; - - cosHalfTheta = - cosHalfTheta; - - } else { - - this.copy( qb ); - - } - - if ( cosHalfTheta >= 1.0 ) { - - this._w = w; - this._x = x; - this._y = y; - this._z = z; - - return this; - - } - - var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta ); - - if ( Math.abs( sinHalfTheta ) < 0.001 ) { - - this._w = 0.5 * ( w + this._w ); - this._x = 0.5 * ( x + this._x ); - this._y = 0.5 * ( y + this._y ); - this._z = 0.5 * ( z + this._z ); - - return this; - - } - - var halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); - var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, - ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; - - this._w = ( w * ratioA + this._w * ratioB ); - this._x = ( x * ratioA + this._x * ratioB ); - this._y = ( y * ratioA + this._y * ratioB ); - this._z = ( z * ratioA + this._z * ratioB ); - - this.onChangeCallback(); - - return this; - - }, - - equals: function ( quaternion ) { - - return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); - - }, - - fromArray: function ( array, offset ) { - - if ( offset === undefined ) offset = 0; - - this._x = array[ offset ]; - this._y = array[ offset + 1 ]; - this._z = array[ offset + 2 ]; - this._w = array[ offset + 3 ]; - - this.onChangeCallback(); - - return this; - - }, - - toArray: function ( array, offset ) { - - if ( array === undefined ) array = []; - if ( offset === undefined ) offset = 0; - - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._w; - - return array; - - }, - - onChange: function ( callback ) { - - this.onChangeCallback = callback; - - return this; - - }, - - onChangeCallback: function () {} - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author kile / http://kile.stravaganza.org/ - * @author philogb / http://blog.thejit.org/ - * @author mikael emtinger / http://gomo.se/ - * @author egraether / http://egraether.com/ - * @author WestLangley / http://github.com/WestLangley - */ - -function Vector3( x, y, z ) { - - this.x = x || 0; - this.y = y || 0; - this.z = z || 0; - -} - -Object.assign( Vector3.prototype, { - - isVector3: true, - - set: function ( x, y, z ) { - - this.x = x; - this.y = y; - this.z = z; - - return this; - - }, - - setScalar: function ( scalar ) { - - this.x = scalar; - this.y = scalar; - this.z = scalar; - - return this; - - }, - - setX: function ( x ) { - - this.x = x; - - return this; - - }, - - setY: function ( y ) { - - this.y = y; - - return this; - - }, - - setZ: function ( z ) { - - this.z = z; - - return this; - - }, - - setComponent: function ( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - default: throw new Error( 'index is out of range: ' + index ); - - } - - return this; - - }, - - getComponent: function ( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - default: throw new Error( 'index is out of range: ' + index ); - - } - - }, - - clone: function () { - - return new this.constructor( this.x, this.y, this.z ); - - }, - - copy: function ( v ) { - - this.x = v.x; - this.y = v.y; - this.z = v.z; - - return this; - - }, - - add: function ( v, w ) { - - if ( w !== undefined ) { - - console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); - return this.addVectors( v, w ); - - } - - this.x += v.x; - this.y += v.y; - this.z += v.z; - - return this; - - }, - - addScalar: function ( s ) { - - this.x += s; - this.y += s; - this.z += s; - - return this; - - }, - - addVectors: function ( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - - return this; - - }, - - addScaledVector: function ( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - - return this; - - }, - - sub: function ( v, w ) { - - if ( w !== undefined ) { - - console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); - return this.subVectors( v, w ); - - } - - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - - return this; - - }, - - subScalar: function ( s ) { - - this.x -= s; - this.y -= s; - this.z -= s; - - return this; - - }, - - subVectors: function ( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - - return this; - - }, - - multiply: function ( v, w ) { - - if ( w !== undefined ) { - - console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' ); - return this.multiplyVectors( v, w ); - - } - - this.x *= v.x; - this.y *= v.y; - this.z *= v.z; - - return this; - - }, - - multiplyScalar: function ( scalar ) { - - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - - return this; - - }, - - multiplyVectors: function ( a, b ) { - - this.x = a.x * b.x; - this.y = a.y * b.y; - this.z = a.z * b.z; - - return this; - - }, - - applyEuler: function () { - - var quaternion = new Quaternion(); - - return function applyEuler( euler ) { - - if ( ! ( euler && euler.isEuler ) ) { - - console.error( 'THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.' ); - - } - - return this.applyQuaternion( quaternion.setFromEuler( euler ) ); - - }; - - }(), - - applyAxisAngle: function () { - - var quaternion = new Quaternion(); - - return function applyAxisAngle( axis, angle ) { - - return this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) ); - - }; - - }(), - - applyMatrix3: function ( m ) { - - var x = this.x, y = this.y, z = this.z; - var e = m.elements; - - this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; - this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; - this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; - - return this; - - }, - - applyMatrix4: function ( m ) { - - var x = this.x, y = this.y, z = this.z; - var e = m.elements; - - var w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); - - this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; - this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; - this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; - - return this; - - }, - - applyQuaternion: function ( q ) { - - var x = this.x, y = this.y, z = this.z; - var qx = q.x, qy = q.y, qz = q.z, qw = q.w; - - // calculate quat * vector - - var ix = qw * x + qy * z - qz * y; - var iy = qw * y + qz * x - qx * z; - var iz = qw * z + qx * y - qy * x; - var iw = - qx * x - qy * y - qz * z; - - // calculate result * inverse quat - - this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; - this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; - this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; - - return this; - - }, - - project: function () { - - var matrix = new Matrix4(); - - return function project( camera ) { - - matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) ); - return this.applyMatrix4( matrix ); - - }; - - }(), - - unproject: function () { - - var matrix = new Matrix4(); - - return function unproject( camera ) { - - matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) ); - return this.applyMatrix4( matrix ); - - }; - - }(), - - transformDirection: function ( m ) { - - // input: THREE.Matrix4 affine matrix - // vector interpreted as a direction - - var x = this.x, y = this.y, z = this.z; - var e = m.elements; - - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; - - return this.normalize(); - - }, - - divide: function ( v ) { - - this.x /= v.x; - this.y /= v.y; - this.z /= v.z; - - return this; - - }, - - divideScalar: function ( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - }, - - min: function ( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - - return this; - - }, - - max: function ( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - - return this; - - }, - - clamp: function ( min, max ) { - - // assumes min < max, componentwise - - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); - - return this; - - }, - - clampScalar: function () { - - var min = new Vector3(); - var max = new Vector3(); - - return function clampScalar( minVal, maxVal ) { - - min.set( minVal, minVal, minVal ); - max.set( maxVal, maxVal, maxVal ); - - return this.clamp( min, max ); - - }; - - }(), - - clampLength: function ( min, max ) { - - var length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - - }, - - floor: function () { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - - return this; - - }, - - ceil: function () { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - - return this; - - }, - - round: function () { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - - return this; - - }, - - roundToZero: function () { - - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); - - return this; - - }, - - negate: function () { - - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - - return this; - - }, - - dot: function ( v ) { - - return this.x * v.x + this.y * v.y + this.z * v.z; - - }, - - // TODO lengthSquared? - - lengthSq: function () { - - return this.x * this.x + this.y * this.y + this.z * this.z; - - }, - - length: function () { - - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); - - }, - - manhattanLength: function () { - - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); - - }, - - normalize: function () { - - return this.divideScalar( this.length() || 1 ); - - }, - - setLength: function ( length ) { - - return this.normalize().multiplyScalar( length ); - - }, - - lerp: function ( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - - return this; - - }, - - lerpVectors: function ( v1, v2, alpha ) { - - return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 ); - - }, - - cross: function ( v, w ) { - - if ( w !== undefined ) { - - console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' ); - return this.crossVectors( v, w ); - - } - - return this.crossVectors( this, v ); - - }, - - crossVectors: function ( a, b ) { - - var ax = a.x, ay = a.y, az = a.z; - var bx = b.x, by = b.y, bz = b.z; - - this.x = ay * bz - az * by; - this.y = az * bx - ax * bz; - this.z = ax * by - ay * bx; - - return this; - - }, - - projectOnVector: function ( vector ) { - - var scalar = vector.dot( this ) / vector.lengthSq(); - - return this.copy( vector ).multiplyScalar( scalar ); - - }, - - projectOnPlane: function () { - - var v1 = new Vector3(); - - return function projectOnPlane( planeNormal ) { - - v1.copy( this ).projectOnVector( planeNormal ); - - return this.sub( v1 ); - - }; - - }(), - - reflect: function () { - - // reflect incident vector off plane orthogonal to normal - // normal is assumed to have unit length - - var v1 = new Vector3(); - - return function reflect( normal ) { - - return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); - - }; - - }(), - - angleTo: function ( v ) { - - var theta = this.dot( v ) / ( Math.sqrt( this.lengthSq() * v.lengthSq() ) ); - - // clamp, to handle numerical problems - - return Math.acos( _Math.clamp( theta, - 1, 1 ) ); - - }, - - distanceTo: function ( v ) { - - return Math.sqrt( this.distanceToSquared( v ) ); - - }, - - distanceToSquared: function ( v ) { - - var dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; - - return dx * dx + dy * dy + dz * dz; - - }, - - manhattanDistanceTo: function ( v ) { - - return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); - - }, - - setFromSpherical: function ( s ) { - - var sinPhiRadius = Math.sin( s.phi ) * s.radius; - - this.x = sinPhiRadius * Math.sin( s.theta ); - this.y = Math.cos( s.phi ) * s.radius; - this.z = sinPhiRadius * Math.cos( s.theta ); - - return this; - - }, - - setFromCylindrical: function ( c ) { - - this.x = c.radius * Math.sin( c.theta ); - this.y = c.y; - this.z = c.radius * Math.cos( c.theta ); - - return this; - - }, - - setFromMatrixPosition: function ( m ) { - - var e = m.elements; - - this.x = e[ 12 ]; - this.y = e[ 13 ]; - this.z = e[ 14 ]; - - return this; - - }, - - setFromMatrixScale: function ( m ) { - - var sx = this.setFromMatrixColumn( m, 0 ).length(); - var sy = this.setFromMatrixColumn( m, 1 ).length(); - var sz = this.setFromMatrixColumn( m, 2 ).length(); - - this.x = sx; - this.y = sy; - this.z = sz; - - return this; - - }, - - setFromMatrixColumn: function ( m, index ) { - - return this.fromArray( m.elements, index * 4 ); - - }, - - equals: function ( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); - - }, - - fromArray: function ( array, offset ) { - - if ( offset === undefined ) offset = 0; - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - - return this; - - }, - - toArray: function ( array, offset ) { - - if ( array === undefined ) array = []; - if ( offset === undefined ) offset = 0; - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - - return array; - - }, - - fromBufferAttribute: function ( attribute, index, offset ) { - - if ( offset !== undefined ) { - - console.warn( 'THREE.Vector3: offset has been removed from .fromBufferAttribute().' ); - - } - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - - return this; - - } - -} ); - -/** - * @author alteredq / http://alteredqualia.com/ - * @author WestLangley / http://github.com/WestLangley - * @author bhouston / http://clara.io - * @author tschw - */ - -function Matrix3() { - - this.elements = [ - - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - - ]; - - if ( arguments.length > 0 ) { - - console.error( 'THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.' ); - - } - -} - -Object.assign( Matrix3.prototype, { - - isMatrix3: true, - - set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { - - var te = this.elements; - - te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; - te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; - te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; - - return this; - - }, - - identity: function () { - - this.set( - - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - - ); - - return this; - - }, - - clone: function () { - - return new this.constructor().fromArray( this.elements ); - - }, - - copy: function ( m ) { - - var te = this.elements; - var me = m.elements; - - te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; - te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; - te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; - - return this; - - }, - - setFromMatrix4: function ( m ) { - - var me = m.elements; - - this.set( - - me[ 0 ], me[ 4 ], me[ 8 ], - me[ 1 ], me[ 5 ], me[ 9 ], - me[ 2 ], me[ 6 ], me[ 10 ] - - ); - - return this; - - }, - - applyToBufferAttribute: function () { - - var v1 = new Vector3(); - - return function applyToBufferAttribute( attribute ) { - - for ( var i = 0, l = attribute.count; i < l; i ++ ) { - - v1.x = attribute.getX( i ); - v1.y = attribute.getY( i ); - v1.z = attribute.getZ( i ); - - v1.applyMatrix3( this ); - - attribute.setXYZ( i, v1.x, v1.y, v1.z ); - - } - - return attribute; - - }; - - }(), - - multiply: function ( m ) { - - return this.multiplyMatrices( this, m ); - - }, - - premultiply: function ( m ) { - - return this.multiplyMatrices( m, this ); - - }, - - multiplyMatrices: function ( a, b ) { - - var ae = a.elements; - var be = b.elements; - var te = this.elements; - - var a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; - var a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; - var a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; - - var b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; - var b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; - var b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; - - te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; - te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; - te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; - - te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; - te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; - te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; - - te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; - te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; - te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; - - return this; - - }, - - multiplyScalar: function ( s ) { - - var te = this.elements; - - te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; - te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; - te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; - - return this; - - }, - - determinant: function () { - - var te = this.elements; - - var a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], - d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], - g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; - - return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; - - }, - - getInverse: function ( matrix, throwOnDegenerate ) { - - if ( matrix && matrix.isMatrix4 ) { - - console.error( "THREE.Matrix3: .getInverse() no longer takes a Matrix4 argument." ); - - } - - var me = matrix.elements, - te = this.elements, - - n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ], - n12 = me[ 3 ], n22 = me[ 4 ], n32 = me[ 5 ], - n13 = me[ 6 ], n23 = me[ 7 ], n33 = me[ 8 ], - - t11 = n33 * n22 - n32 * n23, - t12 = n32 * n13 - n33 * n12, - t13 = n23 * n12 - n22 * n13, - - det = n11 * t11 + n21 * t12 + n31 * t13; - - if ( det === 0 ) { - - var msg = "THREE.Matrix3: .getInverse() can't invert matrix, determinant is 0"; - - if ( throwOnDegenerate === true ) { - - throw new Error( msg ); - - } else { - - console.warn( msg ); - - } - - return this.identity(); - - } - - var detInv = 1 / det; - - te[ 0 ] = t11 * detInv; - te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; - te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; - - te[ 3 ] = t12 * detInv; - te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; - te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; - - te[ 6 ] = t13 * detInv; - te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; - te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; - - return this; - - }, - - transpose: function () { - - var tmp, m = this.elements; - - tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; - tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; - tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; - - return this; - - }, - - getNormalMatrix: function ( matrix4 ) { - - return this.setFromMatrix4( matrix4 ).getInverse( this ).transpose(); - - }, - - transposeIntoArray: function ( r ) { - - var m = this.elements; - - r[ 0 ] = m[ 0 ]; - r[ 1 ] = m[ 3 ]; - r[ 2 ] = m[ 6 ]; - r[ 3 ] = m[ 1 ]; - r[ 4 ] = m[ 4 ]; - r[ 5 ] = m[ 7 ]; - r[ 6 ] = m[ 2 ]; - r[ 7 ] = m[ 5 ]; - r[ 8 ] = m[ 8 ]; - - return this; - - }, - - setUvTransform: function ( tx, ty, sx, sy, rotation, cx, cy ) { - - var c = Math.cos( rotation ); - var s = Math.sin( rotation ); - - this.set( - sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, - - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, - 0, 0, 1 - ); - - }, - - scale: function ( sx, sy ) { - - var te = this.elements; - - te[ 0 ] *= sx; te[ 3 ] *= sx; te[ 6 ] *= sx; - te[ 1 ] *= sy; te[ 4 ] *= sy; te[ 7 ] *= sy; - - return this; - - }, - - rotate: function ( theta ) { - - var c = Math.cos( theta ); - var s = Math.sin( theta ); - - var te = this.elements; - - var a11 = te[ 0 ], a12 = te[ 3 ], a13 = te[ 6 ]; - var a21 = te[ 1 ], a22 = te[ 4 ], a23 = te[ 7 ]; - - te[ 0 ] = c * a11 + s * a21; - te[ 3 ] = c * a12 + s * a22; - te[ 6 ] = c * a13 + s * a23; - - te[ 1 ] = - s * a11 + c * a21; - te[ 4 ] = - s * a12 + c * a22; - te[ 7 ] = - s * a13 + c * a23; - - return this; - - }, - - translate: function ( tx, ty ) { - - var te = this.elements; - - te[ 0 ] += tx * te[ 2 ]; te[ 3 ] += tx * te[ 5 ]; te[ 6 ] += tx * te[ 8 ]; - te[ 1 ] += ty * te[ 2 ]; te[ 4 ] += ty * te[ 5 ]; te[ 7 ] += ty * te[ 8 ]; - - return this; - - }, - - equals: function ( matrix ) { - - var te = this.elements; - var me = matrix.elements; - - for ( var i = 0; i < 9; i ++ ) { - - if ( te[ i ] !== me[ i ] ) return false; - - } - - return true; - - }, - - fromArray: function ( array, offset ) { - - if ( offset === undefined ) offset = 0; - - for ( var i = 0; i < 9; i ++ ) { - - this.elements[ i ] = array[ i + offset ]; - - } - - return this; - - }, - - toArray: function ( array, offset ) { - - if ( array === undefined ) array = []; - if ( offset === undefined ) offset = 0; - - var te = this.elements; - - array[ offset ] = te[ 0 ]; - array[ offset + 1 ] = te[ 1 ]; - array[ offset + 2 ] = te[ 2 ]; - - array[ offset + 3 ] = te[ 3 ]; - array[ offset + 4 ] = te[ 4 ]; - array[ offset + 5 ] = te[ 5 ]; - - array[ offset + 6 ] = te[ 6 ]; - array[ offset + 7 ] = te[ 7 ]; - array[ offset + 8 ] = te[ 8 ]; - - return array; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * @author szimek / https://github.com/szimek/ - */ - -var textureId = 0; - -function Texture( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) { - - Object.defineProperty( this, 'id', { value: textureId ++ } ); - - this.uuid = _Math.generateUUID(); - - this.name = ''; - - this.image = image !== undefined ? image : Texture.DEFAULT_IMAGE; - this.mipmaps = []; - - this.mapping = mapping !== undefined ? mapping : Texture.DEFAULT_MAPPING; - - this.wrapS = wrapS !== undefined ? wrapS : ClampToEdgeWrapping; - this.wrapT = wrapT !== undefined ? wrapT : ClampToEdgeWrapping; - - this.magFilter = magFilter !== undefined ? magFilter : LinearFilter; - this.minFilter = minFilter !== undefined ? minFilter : LinearMipMapLinearFilter; - - this.anisotropy = anisotropy !== undefined ? anisotropy : 1; - - this.format = format !== undefined ? format : RGBAFormat; - this.type = type !== undefined ? type : UnsignedByteType; - - this.offset = new Vector2( 0, 0 ); - this.repeat = new Vector2( 1, 1 ); - this.center = new Vector2( 0, 0 ); - this.rotation = 0; - - this.matrixAutoUpdate = true; - this.matrix = new Matrix3(); - - this.generateMipmaps = true; - this.premultiplyAlpha = false; - this.flipY = true; - this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) - - // Values of encoding !== THREE.LinearEncoding only supported on map, envMap and emissiveMap. - // - // Also changing the encoding after already used by a Material will not automatically make the Material - // update. You need to explicitly call Material.needsUpdate to trigger it to recompile. - this.encoding = encoding !== undefined ? encoding : LinearEncoding; - - this.version = 0; - this.onUpdate = null; - -} - -Texture.DEFAULT_IMAGE = undefined; -Texture.DEFAULT_MAPPING = UVMapping; - -Object.defineProperty( Texture.prototype, "needsUpdate", { - - set: function ( value ) { - - if ( value === true ) this.version ++; - - } - -} ); - -Object.assign( Texture.prototype, EventDispatcher.prototype, { - - constructor: Texture, - - isTexture: true, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( source ) { - - this.name = source.name; - - this.image = source.image; - this.mipmaps = source.mipmaps.slice( 0 ); - - this.mapping = source.mapping; - - this.wrapS = source.wrapS; - this.wrapT = source.wrapT; - - this.magFilter = source.magFilter; - this.minFilter = source.minFilter; - - this.anisotropy = source.anisotropy; - - this.format = source.format; - this.type = source.type; - - this.offset.copy( source.offset ); - this.repeat.copy( source.repeat ); - this.center.copy( source.center ); - this.rotation = source.rotation; - - this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrix.copy( source.matrix ); - - this.generateMipmaps = source.generateMipmaps; - this.premultiplyAlpha = source.premultiplyAlpha; - this.flipY = source.flipY; - this.unpackAlignment = source.unpackAlignment; - this.encoding = source.encoding; - - return this; - - }, - - toJSON: function ( meta ) { - - var isRootObject = ( meta === undefined || typeof meta === 'string' ); - - if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { - - return meta.textures[ this.uuid ]; - - } - - function getDataURL( image ) { - - var canvas; - - if ( image instanceof HTMLCanvasElement ) { - - canvas = image; - - } else { - - canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); - canvas.width = image.width; - canvas.height = image.height; - - var context = canvas.getContext( '2d' ); - - if ( image instanceof ImageData ) { - - context.putImageData( image, 0, 0 ); - - } else { - - context.drawImage( image, 0, 0, image.width, image.height ); - - } - - } - - if ( canvas.width > 2048 || canvas.height > 2048 ) { - - return canvas.toDataURL( 'image/jpeg', 0.6 ); - - } else { - - return canvas.toDataURL( 'image/png' ); - - } - - } - - var output = { - metadata: { - version: 4.5, - type: 'Texture', - generator: 'Texture.toJSON' - }, - - uuid: this.uuid, - name: this.name, - - mapping: this.mapping, - - repeat: [ this.repeat.x, this.repeat.y ], - offset: [ this.offset.x, this.offset.y ], - center: [ this.center.x, this.center.y ], - rotation: this.rotation, - - wrap: [ this.wrapS, this.wrapT ], - - minFilter: this.minFilter, - magFilter: this.magFilter, - anisotropy: this.anisotropy, - - flipY: this.flipY - }; - - if ( this.image !== undefined ) { - - // TODO: Move to THREE.Image - - var image = this.image; - - if ( image.uuid === undefined ) { - - image.uuid = _Math.generateUUID(); // UGH - - } - - if ( ! isRootObject && meta.images[ image.uuid ] === undefined ) { - - meta.images[ image.uuid ] = { - uuid: image.uuid, - url: getDataURL( image ) - }; - - } - - output.image = image.uuid; - - } - - if ( ! isRootObject ) { - - meta.textures[ this.uuid ] = output; - - } - - return output; - - }, - - dispose: function () { - - this.dispatchEvent( { type: 'dispose' } ); - - }, - - transformUv: function ( uv ) { - - if ( this.mapping !== UVMapping ) return; - - uv.applyMatrix3( this.matrix ); - - if ( uv.x < 0 || uv.x > 1 ) { - - switch ( this.wrapS ) { - - case RepeatWrapping: - - uv.x = uv.x - Math.floor( uv.x ); - break; - - case ClampToEdgeWrapping: - - uv.x = uv.x < 0 ? 0 : 1; - break; - - case MirroredRepeatWrapping: - - if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { - - uv.x = Math.ceil( uv.x ) - uv.x; - - } else { - - uv.x = uv.x - Math.floor( uv.x ); - - } - break; - - } - - } - - if ( uv.y < 0 || uv.y > 1 ) { - - switch ( this.wrapT ) { - - case RepeatWrapping: - - uv.y = uv.y - Math.floor( uv.y ); - break; - - case ClampToEdgeWrapping: - - uv.y = uv.y < 0 ? 0 : 1; - break; - - case MirroredRepeatWrapping: - - if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { - - uv.y = Math.ceil( uv.y ) - uv.y; - - } else { - - uv.y = uv.y - Math.floor( uv.y ); - - } - break; - - } - - } - - if ( this.flipY ) { - - uv.y = 1 - uv.y; - - } - - } - -} ); - -/** - * @author supereggbert / http://www.paulbrunt.co.uk/ - * @author philogb / http://blog.thejit.org/ - * @author mikael emtinger / http://gomo.se/ - * @author egraether / http://egraether.com/ - * @author WestLangley / http://github.com/WestLangley - */ - -function Vector4( x, y, z, w ) { - - this.x = x || 0; - this.y = y || 0; - this.z = z || 0; - this.w = ( w !== undefined ) ? w : 1; - -} - -Object.assign( Vector4.prototype, { - - isVector4: true, - - set: function ( x, y, z, w ) { - - this.x = x; - this.y = y; - this.z = z; - this.w = w; - - return this; - - }, - - setScalar: function ( scalar ) { - - this.x = scalar; - this.y = scalar; - this.z = scalar; - this.w = scalar; - - return this; - - }, - - setX: function ( x ) { - - this.x = x; - - return this; - - }, - - setY: function ( y ) { - - this.y = y; - - return this; - - }, - - setZ: function ( z ) { - - this.z = z; - - return this; - - }, - - setW: function ( w ) { - - this.w = w; - - return this; - - }, - - setComponent: function ( index, value ) { - - switch ( index ) { - - case 0: this.x = value; break; - case 1: this.y = value; break; - case 2: this.z = value; break; - case 3: this.w = value; break; - default: throw new Error( 'index is out of range: ' + index ); - - } - - return this; - - }, - - getComponent: function ( index ) { - - switch ( index ) { - - case 0: return this.x; - case 1: return this.y; - case 2: return this.z; - case 3: return this.w; - default: throw new Error( 'index is out of range: ' + index ); - - } - - }, - - clone: function () { - - return new this.constructor( this.x, this.y, this.z, this.w ); - - }, - - copy: function ( v ) { - - this.x = v.x; - this.y = v.y; - this.z = v.z; - this.w = ( v.w !== undefined ) ? v.w : 1; - - return this; - - }, - - add: function ( v, w ) { - - if ( w !== undefined ) { - - console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); - return this.addVectors( v, w ); - - } - - this.x += v.x; - this.y += v.y; - this.z += v.z; - this.w += v.w; - - return this; - - }, - - addScalar: function ( s ) { - - this.x += s; - this.y += s; - this.z += s; - this.w += s; - - return this; - - }, - - addVectors: function ( a, b ) { - - this.x = a.x + b.x; - this.y = a.y + b.y; - this.z = a.z + b.z; - this.w = a.w + b.w; - - return this; - - }, - - addScaledVector: function ( v, s ) { - - this.x += v.x * s; - this.y += v.y * s; - this.z += v.z * s; - this.w += v.w * s; - - return this; - - }, - - sub: function ( v, w ) { - - if ( w !== undefined ) { - - console.warn( 'THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); - return this.subVectors( v, w ); - - } - - this.x -= v.x; - this.y -= v.y; - this.z -= v.z; - this.w -= v.w; - - return this; - - }, - - subScalar: function ( s ) { - - this.x -= s; - this.y -= s; - this.z -= s; - this.w -= s; - - return this; - - }, - - subVectors: function ( a, b ) { - - this.x = a.x - b.x; - this.y = a.y - b.y; - this.z = a.z - b.z; - this.w = a.w - b.w; - - return this; - - }, - - multiplyScalar: function ( scalar ) { - - this.x *= scalar; - this.y *= scalar; - this.z *= scalar; - this.w *= scalar; - - return this; - - }, - - applyMatrix4: function ( m ) { - - var x = this.x, y = this.y, z = this.z, w = this.w; - var e = m.elements; - - this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; - this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; - this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; - this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; - - return this; - - }, - - divideScalar: function ( scalar ) { - - return this.multiplyScalar( 1 / scalar ); - - }, - - setAxisAngleFromQuaternion: function ( q ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm - - // q is assumed to be normalized - - this.w = 2 * Math.acos( q.w ); - - var s = Math.sqrt( 1 - q.w * q.w ); - - if ( s < 0.0001 ) { - - this.x = 1; - this.y = 0; - this.z = 0; - - } else { - - this.x = q.x / s; - this.y = q.y / s; - this.z = q.z / s; - - } - - return this; - - }, - - setAxisAngleFromRotationMatrix: function ( m ) { - - // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - var angle, x, y, z, // variables for result - epsilon = 0.01, // margin to allow for rounding errors - epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees - - te = m.elements, - - m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], - m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], - m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - - if ( ( Math.abs( m12 - m21 ) < epsilon ) && - ( Math.abs( m13 - m31 ) < epsilon ) && - ( Math.abs( m23 - m32 ) < epsilon ) ) { - - // singularity found - // first check for identity matrix which must have +1 for all terms - // in leading diagonal and zero in other terms - - if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && - ( Math.abs( m13 + m31 ) < epsilon2 ) && - ( Math.abs( m23 + m32 ) < epsilon2 ) && - ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { - - // this singularity is identity matrix so angle = 0 - - this.set( 1, 0, 0, 0 ); - - return this; // zero angle, arbitrary axis - - } - - // otherwise this singularity is angle = 180 - - angle = Math.PI; - - var xx = ( m11 + 1 ) / 2; - var yy = ( m22 + 1 ) / 2; - var zz = ( m33 + 1 ) / 2; - var xy = ( m12 + m21 ) / 4; - var xz = ( m13 + m31 ) / 4; - var yz = ( m23 + m32 ) / 4; - - if ( ( xx > yy ) && ( xx > zz ) ) { - - // m11 is the largest diagonal term - - if ( xx < epsilon ) { - - x = 0; - y = 0.707106781; - z = 0.707106781; - - } else { - - x = Math.sqrt( xx ); - y = xy / x; - z = xz / x; - - } - - } else if ( yy > zz ) { - - // m22 is the largest diagonal term - - if ( yy < epsilon ) { - - x = 0.707106781; - y = 0; - z = 0.707106781; - - } else { - - y = Math.sqrt( yy ); - x = xy / y; - z = yz / y; - - } - - } else { - - // m33 is the largest diagonal term so base result on this - - if ( zz < epsilon ) { - - x = 0.707106781; - y = 0.707106781; - z = 0; - - } else { - - z = Math.sqrt( zz ); - x = xz / z; - y = yz / z; - - } - - } - - this.set( x, y, z, angle ); - - return this; // return 180 deg rotation - - } - - // as we have reached here there are no singularities so we can handle normally - - var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + - ( m13 - m31 ) * ( m13 - m31 ) + - ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize - - if ( Math.abs( s ) < 0.001 ) s = 1; - - // prevent divide by zero, should not happen if matrix is orthogonal and should be - // caught by singularity test above, but I've left it in just in case - - this.x = ( m32 - m23 ) / s; - this.y = ( m13 - m31 ) / s; - this.z = ( m21 - m12 ) / s; - this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); - - return this; - - }, - - min: function ( v ) { - - this.x = Math.min( this.x, v.x ); - this.y = Math.min( this.y, v.y ); - this.z = Math.min( this.z, v.z ); - this.w = Math.min( this.w, v.w ); - - return this; - - }, - - max: function ( v ) { - - this.x = Math.max( this.x, v.x ); - this.y = Math.max( this.y, v.y ); - this.z = Math.max( this.z, v.z ); - this.w = Math.max( this.w, v.w ); - - return this; - - }, - - clamp: function ( min, max ) { - - // assumes min < max, componentwise - - this.x = Math.max( min.x, Math.min( max.x, this.x ) ); - this.y = Math.max( min.y, Math.min( max.y, this.y ) ); - this.z = Math.max( min.z, Math.min( max.z, this.z ) ); - this.w = Math.max( min.w, Math.min( max.w, this.w ) ); - - return this; - - }, - - clampScalar: function () { - - var min, max; - - return function clampScalar( minVal, maxVal ) { - - if ( min === undefined ) { - - min = new Vector4(); - max = new Vector4(); - - } - - min.set( minVal, minVal, minVal, minVal ); - max.set( maxVal, maxVal, maxVal, maxVal ); - - return this.clamp( min, max ); - - }; - - }(), - - clampLength: function ( min, max ) { - - var length = this.length(); - - return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); - - }, - - floor: function () { - - this.x = Math.floor( this.x ); - this.y = Math.floor( this.y ); - this.z = Math.floor( this.z ); - this.w = Math.floor( this.w ); - - return this; - - }, - - ceil: function () { - - this.x = Math.ceil( this.x ); - this.y = Math.ceil( this.y ); - this.z = Math.ceil( this.z ); - this.w = Math.ceil( this.w ); - - return this; - - }, - - round: function () { - - this.x = Math.round( this.x ); - this.y = Math.round( this.y ); - this.z = Math.round( this.z ); - this.w = Math.round( this.w ); - - return this; - - }, - - roundToZero: function () { - - this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); - this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); - this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); - this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w ); - - return this; - - }, - - negate: function () { - - this.x = - this.x; - this.y = - this.y; - this.z = - this.z; - this.w = - this.w; - - return this; - - }, - - dot: function ( v ) { - - return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; - - }, - - lengthSq: function () { - - return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; - - }, - - length: function () { - - return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); - - }, - - manhattanLength: function () { - - return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); - - }, - - normalize: function () { - - return this.divideScalar( this.length() || 1 ); - - }, - - setLength: function ( length ) { - - return this.normalize().multiplyScalar( length ); - - }, - - lerp: function ( v, alpha ) { - - this.x += ( v.x - this.x ) * alpha; - this.y += ( v.y - this.y ) * alpha; - this.z += ( v.z - this.z ) * alpha; - this.w += ( v.w - this.w ) * alpha; - - return this; - - }, - - lerpVectors: function ( v1, v2, alpha ) { - - return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 ); - - }, - - equals: function ( v ) { - - return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); - - }, - - fromArray: function ( array, offset ) { - - if ( offset === undefined ) offset = 0; - - this.x = array[ offset ]; - this.y = array[ offset + 1 ]; - this.z = array[ offset + 2 ]; - this.w = array[ offset + 3 ]; - - return this; - - }, - - toArray: function ( array, offset ) { - - if ( array === undefined ) array = []; - if ( offset === undefined ) offset = 0; - - array[ offset ] = this.x; - array[ offset + 1 ] = this.y; - array[ offset + 2 ] = this.z; - array[ offset + 3 ] = this.w; - - return array; - - }, - - fromBufferAttribute: function ( attribute, index, offset ) { - - if ( offset !== undefined ) { - - console.warn( 'THREE.Vector4: offset has been removed from .fromBufferAttribute().' ); - - } - - this.x = attribute.getX( index ); - this.y = attribute.getY( index ); - this.z = attribute.getZ( index ); - this.w = attribute.getW( index ); - - return this; - - } - -} ); - -/** - * @author szimek / https://github.com/szimek/ - * @author alteredq / http://alteredqualia.com/ - * @author Marius Kintel / https://github.com/kintel - */ - -/* - In options, we can specify: - * Texture parameters for an auto-generated target texture - * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers -*/ -function WebGLRenderTarget( width, height, options ) { - - this.uuid = _Math.generateUUID(); - - this.width = width; - this.height = height; - - this.scissor = new Vector4( 0, 0, width, height ); - this.scissorTest = false; - - this.viewport = new Vector4( 0, 0, width, height ); - - options = options || {}; - - if ( options.minFilter === undefined ) options.minFilter = LinearFilter; - - this.texture = new Texture( undefined, undefined, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding ); - - this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; - this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true; - this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null; - -} - -Object.assign( WebGLRenderTarget.prototype, EventDispatcher.prototype, { - - isWebGLRenderTarget: true, - - setSize: function ( width, height ) { - - if ( this.width !== width || this.height !== height ) { - - this.width = width; - this.height = height; - - this.dispose(); - - } - - this.viewport.set( 0, 0, width, height ); - this.scissor.set( 0, 0, width, height ); - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( source ) { - - this.width = source.width; - this.height = source.height; - - this.viewport.copy( source.viewport ); - - this.texture = source.texture.clone(); - - this.depthBuffer = source.depthBuffer; - this.stencilBuffer = source.stencilBuffer; - this.depthTexture = source.depthTexture; - - return this; - - }, - - dispose: function () { - - this.dispatchEvent( { type: 'dispose' } ); - - } - -} ); - -/** - * @author alteredq / http://alteredqualia.com - */ - -function WebGLRenderTargetCube( width, height, options ) { - - WebGLRenderTarget.call( this, width, height, options ); - - this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5 - this.activeMipMapLevel = 0; - -} - -WebGLRenderTargetCube.prototype = Object.create( WebGLRenderTarget.prototype ); -WebGLRenderTargetCube.prototype.constructor = WebGLRenderTargetCube; - -WebGLRenderTargetCube.prototype.isWebGLRenderTargetCube = true; - -/** - * @author alteredq / http://alteredqualia.com/ - */ - -function DataTexture( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) { - - Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ); - - this.image = { data: data, width: width, height: height }; - - this.magFilter = magFilter !== undefined ? magFilter : NearestFilter; - this.minFilter = minFilter !== undefined ? minFilter : NearestFilter; - - this.generateMipmaps = false; - this.flipY = false; - this.unpackAlignment = 1; - -} - -DataTexture.prototype = Object.create( Texture.prototype ); -DataTexture.prototype.constructor = DataTexture; - -DataTexture.prototype.isDataTexture = true; - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function CubeTexture( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) { - - images = images !== undefined ? images : []; - mapping = mapping !== undefined ? mapping : CubeReflectionMapping; - - Texture.call( this, images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ); - - this.flipY = false; - -} - -CubeTexture.prototype = Object.create( Texture.prototype ); -CubeTexture.prototype.constructor = CubeTexture; - -CubeTexture.prototype.isCubeTexture = true; - -Object.defineProperty( CubeTexture.prototype, 'images', { - - get: function () { - - return this.image; - - }, - - set: function ( value ) { - - this.image = value; - - } - -} ); - -/** - * @author tschw - * - * Uniforms of a program. - * Those form a tree structure with a special top-level container for the root, - * which you get by calling 'new WebGLUniforms( gl, program, renderer )'. - * - * - * Properties of inner nodes including the top-level container: - * - * .seq - array of nested uniforms - * .map - nested uniforms by name - * - * - * Methods of all nodes except the top-level container: - * - * .setValue( gl, value, [renderer] ) - * - * uploads a uniform value(s) - * the 'renderer' parameter is needed for sampler uniforms - * - * - * Static methods of the top-level container (renderer factorizations): - * - * .upload( gl, seq, values, renderer ) - * - * sets uniforms in 'seq' to 'values[id].value' - * - * .seqWithValue( seq, values ) : filteredSeq - * - * filters 'seq' entries with corresponding entry in values - * - * - * Methods of the top-level container (renderer factorizations): - * - * .setValue( gl, name, value ) - * - * sets uniform with name 'name' to 'value' - * - * .set( gl, obj, prop ) - * - * sets uniform from object and property with same name than uniform - * - * .setOptional( gl, obj, prop ) - * - * like .set for an optional property of the object - * - */ - -var emptyTexture = new Texture(); -var emptyCubeTexture = new CubeTexture(); - -// --- Base for inner nodes (including the root) --- - -function UniformContainer() { - - this.seq = []; - this.map = {}; - -} - -// --- Utilities --- - -// Array Caches (provide typed arrays for temporary by size) - -var arrayCacheF32 = []; -var arrayCacheI32 = []; - -// Float32Array caches used for uploading Matrix uniforms - -var mat4array = new Float32Array( 16 ); -var mat3array = new Float32Array( 9 ); - -// Flattening for arrays of vectors and matrices - -function flatten( array, nBlocks, blockSize ) { - - var firstElem = array[ 0 ]; - - if ( firstElem <= 0 || firstElem > 0 ) return array; - // unoptimized: ! isNaN( firstElem ) - // see http://jacksondunstan.com/articles/983 - - var n = nBlocks * blockSize, - r = arrayCacheF32[ n ]; - - if ( r === undefined ) { - - r = new Float32Array( n ); - arrayCacheF32[ n ] = r; - - } - - if ( nBlocks !== 0 ) { - - firstElem.toArray( r, 0 ); - - for ( var i = 1, offset = 0; i !== nBlocks; ++ i ) { - - offset += blockSize; - array[ i ].toArray( r, offset ); - - } - - } - - return r; - -} - -// Texture unit allocation - -function allocTexUnits( renderer, n ) { - - var r = arrayCacheI32[ n ]; - - if ( r === undefined ) { - - r = new Int32Array( n ); - arrayCacheI32[ n ] = r; - - } - - for ( var i = 0; i !== n; ++ i ) - r[ i ] = renderer.allocTextureUnit(); - - return r; - -} - -// --- Setters --- - -// Note: Defining these methods externally, because they come in a bunch -// and this way their names minify. - -// Single scalar - -function setValue1f( gl, v ) { - - gl.uniform1f( this.addr, v ); - -} - -function setValue1i( gl, v ) { - - gl.uniform1i( this.addr, v ); - -} - -// Single float vector (from flat array or THREE.VectorN) - -function setValue2fv( gl, v ) { - - if ( v.x === undefined ) { - - gl.uniform2fv( this.addr, v ); - - } else { - - gl.uniform2f( this.addr, v.x, v.y ); - - } - -} - -function setValue3fv( gl, v ) { - - if ( v.x !== undefined ) { - - gl.uniform3f( this.addr, v.x, v.y, v.z ); - - } else if ( v.r !== undefined ) { - - gl.uniform3f( this.addr, v.r, v.g, v.b ); - - } else { - - gl.uniform3fv( this.addr, v ); - - } - -} - -function setValue4fv( gl, v ) { - - if ( v.x === undefined ) { - - gl.uniform4fv( this.addr, v ); - - } else { - - gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); - - } - -} - -// Single matrix (from flat array or MatrixN) - -function setValue2fm( gl, v ) { - - gl.uniformMatrix2fv( this.addr, false, v.elements || v ); - -} - -function setValue3fm( gl, v ) { - - if ( v.elements === undefined ) { - - gl.uniformMatrix3fv( this.addr, false, v ); - - } else { - - mat3array.set( v.elements ); - gl.uniformMatrix3fv( this.addr, false, mat3array ); - - } - -} - -function setValue4fm( gl, v ) { - - if ( v.elements === undefined ) { - - gl.uniformMatrix4fv( this.addr, false, v ); - - } else { - - mat4array.set( v.elements ); - gl.uniformMatrix4fv( this.addr, false, mat4array ); - - } - -} - -// Single texture (2D / Cube) - -function setValueT1( gl, v, renderer ) { - - var unit = renderer.allocTextureUnit(); - gl.uniform1i( this.addr, unit ); - renderer.setTexture2D( v || emptyTexture, unit ); - -} - -function setValueT6( gl, v, renderer ) { - - var unit = renderer.allocTextureUnit(); - gl.uniform1i( this.addr, unit ); - renderer.setTextureCube( v || emptyCubeTexture, unit ); - -} - -// Integer / Boolean vectors or arrays thereof (always flat arrays) - -function setValue2iv( gl, v ) { - - gl.uniform2iv( this.addr, v ); - -} - -function setValue3iv( gl, v ) { - - gl.uniform3iv( this.addr, v ); - -} - -function setValue4iv( gl, v ) { - - gl.uniform4iv( this.addr, v ); - -} - -// Helper to pick the right setter for the singular case - -function getSingularSetter( type ) { - - switch ( type ) { - - case 0x1406: return setValue1f; // FLOAT - case 0x8b50: return setValue2fv; // _VEC2 - case 0x8b51: return setValue3fv; // _VEC3 - case 0x8b52: return setValue4fv; // _VEC4 - - case 0x8b5a: return setValue2fm; // _MAT2 - case 0x8b5b: return setValue3fm; // _MAT3 - case 0x8b5c: return setValue4fm; // _MAT4 - - case 0x8b5e: case 0x8d66: return setValueT1; // SAMPLER_2D, SAMPLER_EXTERNAL_OES - case 0x8b60: return setValueT6; // SAMPLER_CUBE - - case 0x1404: case 0x8b56: return setValue1i; // INT, BOOL - case 0x8b53: case 0x8b57: return setValue2iv; // _VEC2 - case 0x8b54: case 0x8b58: return setValue3iv; // _VEC3 - case 0x8b55: case 0x8b59: return setValue4iv; // _VEC4 - - } - -} - -// Array of scalars - -function setValue1fv( gl, v ) { - - gl.uniform1fv( this.addr, v ); - -} -function setValue1iv( gl, v ) { - - gl.uniform1iv( this.addr, v ); - -} - -// Array of vectors (flat or from THREE classes) - -function setValueV2a( gl, v ) { - - gl.uniform2fv( this.addr, flatten( v, this.size, 2 ) ); - -} - -function setValueV3a( gl, v ) { - - gl.uniform3fv( this.addr, flatten( v, this.size, 3 ) ); - -} - -function setValueV4a( gl, v ) { - - gl.uniform4fv( this.addr, flatten( v, this.size, 4 ) ); - -} - -// Array of matrices (flat or from THREE clases) - -function setValueM2a( gl, v ) { - - gl.uniformMatrix2fv( this.addr, false, flatten( v, this.size, 4 ) ); - -} - -function setValueM3a( gl, v ) { - - gl.uniformMatrix3fv( this.addr, false, flatten( v, this.size, 9 ) ); - -} - -function setValueM4a( gl, v ) { - - gl.uniformMatrix4fv( this.addr, false, flatten( v, this.size, 16 ) ); - -} - -// Array of textures (2D / Cube) - -function setValueT1a( gl, v, renderer ) { - - var n = v.length, - units = allocTexUnits( renderer, n ); - - gl.uniform1iv( this.addr, units ); - - for ( var i = 0; i !== n; ++ i ) { - - renderer.setTexture2D( v[ i ] || emptyTexture, units[ i ] ); - - } - -} - -function setValueT6a( gl, v, renderer ) { - - var n = v.length, - units = allocTexUnits( renderer, n ); - - gl.uniform1iv( this.addr, units ); - - for ( var i = 0; i !== n; ++ i ) { - - renderer.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); - - } - -} - -// Helper to pick the right setter for a pure (bottom-level) array - -function getPureArraySetter( type ) { - - switch ( type ) { - - case 0x1406: return setValue1fv; // FLOAT - case 0x8b50: return setValueV2a; // _VEC2 - case 0x8b51: return setValueV3a; // _VEC3 - case 0x8b52: return setValueV4a; // _VEC4 - - case 0x8b5a: return setValueM2a; // _MAT2 - case 0x8b5b: return setValueM3a; // _MAT3 - case 0x8b5c: return setValueM4a; // _MAT4 - - case 0x8b5e: return setValueT1a; // SAMPLER_2D - case 0x8b60: return setValueT6a; // SAMPLER_CUBE - - case 0x1404: case 0x8b56: return setValue1iv; // INT, BOOL - case 0x8b53: case 0x8b57: return setValue2iv; // _VEC2 - case 0x8b54: case 0x8b58: return setValue3iv; // _VEC3 - case 0x8b55: case 0x8b59: return setValue4iv; // _VEC4 - - } - -} - -// --- Uniform Classes --- - -function SingleUniform( id, activeInfo, addr ) { - - this.id = id; - this.addr = addr; - this.setValue = getSingularSetter( activeInfo.type ); - - // this.path = activeInfo.name; // DEBUG - -} - -function PureArrayUniform( id, activeInfo, addr ) { - - this.id = id; - this.addr = addr; - this.size = activeInfo.size; - this.setValue = getPureArraySetter( activeInfo.type ); - - // this.path = activeInfo.name; // DEBUG - -} - -function StructuredUniform( id ) { - - this.id = id; - - UniformContainer.call( this ); // mix-in - -} - -StructuredUniform.prototype.setValue = function ( gl, value ) { - - // Note: Don't need an extra 'renderer' parameter, since samplers - // are not allowed in structured uniforms. - - var seq = this.seq; - - for ( var i = 0, n = seq.length; i !== n; ++ i ) { - - var u = seq[ i ]; - u.setValue( gl, value[ u.id ] ); - - } - -}; - -// --- Top-level --- - -// Parser - builds up the property tree from the path strings - -var RePathPart = /([\w\d_]+)(\])?(\[|\.)?/g; - -// extracts -// - the identifier (member name or array index) -// - followed by an optional right bracket (found when array index) -// - followed by an optional left bracket or dot (type of subscript) -// -// Note: These portions can be read in a non-overlapping fashion and -// allow straightforward parsing of the hierarchy that WebGL encodes -// in the uniform names. - -function addUniform( container, uniformObject ) { - - container.seq.push( uniformObject ); - container.map[ uniformObject.id ] = uniformObject; - -} - -function parseUniform( activeInfo, addr, container ) { - - var path = activeInfo.name, - pathLength = path.length; - - // reset RegExp object, because of the early exit of a previous run - RePathPart.lastIndex = 0; - - for ( ; ; ) { - - var match = RePathPart.exec( path ), - matchEnd = RePathPart.lastIndex, - - id = match[ 1 ], - idIsIndex = match[ 2 ] === ']', - subscript = match[ 3 ]; - - if ( idIsIndex ) id = id | 0; // convert to integer - - if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { - - // bare name or "pure" bottom-level array "[0]" suffix - - addUniform( container, subscript === undefined ? - new SingleUniform( id, activeInfo, addr ) : - new PureArrayUniform( id, activeInfo, addr ) ); - - break; - - } else { - - // step into inner node / create it in case it doesn't exist - - var map = container.map, next = map[ id ]; - - if ( next === undefined ) { - - next = new StructuredUniform( id ); - addUniform( container, next ); - - } - - container = next; - - } - - } - -} - -// Root Container - -function WebGLUniforms( gl, program, renderer ) { - - UniformContainer.call( this ); - - this.renderer = renderer; - - var n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); - - for ( var i = 0; i < n; ++ i ) { - - var info = gl.getActiveUniform( program, i ), - path = info.name, - addr = gl.getUniformLocation( program, path ); - - parseUniform( info, addr, this ); - - } - -} - -WebGLUniforms.prototype.setValue = function ( gl, name, value ) { - - var u = this.map[ name ]; - - if ( u !== undefined ) u.setValue( gl, value, this.renderer ); - -}; - -WebGLUniforms.prototype.setOptional = function ( gl, object, name ) { - - var v = object[ name ]; - - if ( v !== undefined ) this.setValue( gl, name, v ); - -}; - - -// Static interface - -WebGLUniforms.upload = function ( gl, seq, values, renderer ) { - - for ( var i = 0, n = seq.length; i !== n; ++ i ) { - - var u = seq[ i ], - v = values[ u.id ]; - - if ( v.needsUpdate !== false ) { - - // note: always updating when .needsUpdate is undefined - u.setValue( gl, v.value, renderer ); - - } - - } - -}; - -WebGLUniforms.seqWithValue = function ( seq, values ) { - - var r = []; - - for ( var i = 0, n = seq.length; i !== n; ++ i ) { - - var u = seq[ i ]; - if ( u.id in values ) r.push( u ); - - } - - return r; - -}; - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -var ColorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, - 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, - 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, - 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, - 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, - 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, - 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, - 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, - 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, - 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, - 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, - 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, - 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, - 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, - 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, - 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, - 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, - 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, - 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, - 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, - 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, - 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, - 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, - 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; - -function Color( r, g, b ) { - - if ( g === undefined && b === undefined ) { - - // r is THREE.Color, hex or string - return this.set( r ); - - } - - return this.setRGB( r, g, b ); - -} - -Object.assign( Color.prototype, { - - isColor: true, - - r: 1, g: 1, b: 1, - - set: function ( value ) { - - if ( value && value.isColor ) { - - this.copy( value ); - - } else if ( typeof value === 'number' ) { - - this.setHex( value ); - - } else if ( typeof value === 'string' ) { - - this.setStyle( value ); - - } - - return this; - - }, - - setScalar: function ( scalar ) { - - this.r = scalar; - this.g = scalar; - this.b = scalar; - - return this; - - }, - - setHex: function ( hex ) { - - hex = Math.floor( hex ); - - this.r = ( hex >> 16 & 255 ) / 255; - this.g = ( hex >> 8 & 255 ) / 255; - this.b = ( hex & 255 ) / 255; - - return this; - - }, - - setRGB: function ( r, g, b ) { - - this.r = r; - this.g = g; - this.b = b; - - return this; - - }, - - setHSL: function () { - - function hue2rgb( p, q, t ) { - - if ( t < 0 ) t += 1; - if ( t > 1 ) t -= 1; - if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; - if ( t < 1 / 2 ) return q; - if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); - return p; - - } - - return function setHSL( h, s, l ) { - - // h,s,l ranges are in 0.0 - 1.0 - h = _Math.euclideanModulo( h, 1 ); - s = _Math.clamp( s, 0, 1 ); - l = _Math.clamp( l, 0, 1 ); - - if ( s === 0 ) { - - this.r = this.g = this.b = l; - - } else { - - var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); - var q = ( 2 * l ) - p; - - this.r = hue2rgb( q, p, h + 1 / 3 ); - this.g = hue2rgb( q, p, h ); - this.b = hue2rgb( q, p, h - 1 / 3 ); - - } - - return this; - - }; - - }(), - - setStyle: function ( style ) { - - function handleAlpha( string ) { - - if ( string === undefined ) return; - - if ( parseFloat( string ) < 1 ) { - - console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); - - } - - } - - - var m; - - if ( m = /^((?:rgb|hsl)a?)\(\s*([^\)]*)\)/.exec( style ) ) { - - // rgb / hsl - - var color; - var name = m[ 1 ]; - var components = m[ 2 ]; - - switch ( name ) { - - case 'rgb': - case 'rgba': - - if ( color = /^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) { - - // rgb(255,0,0) rgba(255,0,0,0.5) - this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255; - this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255; - this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255; - - handleAlpha( color[ 5 ] ); - - return this; - - } - - if ( color = /^(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) { - - // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) - this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100; - this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100; - this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100; - - handleAlpha( color[ 5 ] ); - - return this; - - } - - break; - - case 'hsl': - case 'hsla': - - if ( color = /^([0-9]*\.?[0-9]+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) { - - // hsl(120,50%,50%) hsla(120,50%,50%,0.5) - var h = parseFloat( color[ 1 ] ) / 360; - var s = parseInt( color[ 2 ], 10 ) / 100; - var l = parseInt( color[ 3 ], 10 ) / 100; - - handleAlpha( color[ 5 ] ); - - return this.setHSL( h, s, l ); - - } - - break; - - } - - } else if ( m = /^\#([A-Fa-f0-9]+)$/.exec( style ) ) { - - // hex color - - var hex = m[ 1 ]; - var size = hex.length; - - if ( size === 3 ) { - - // #ff0 - this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 0 ), 16 ) / 255; - this.g = parseInt( hex.charAt( 1 ) + hex.charAt( 1 ), 16 ) / 255; - this.b = parseInt( hex.charAt( 2 ) + hex.charAt( 2 ), 16 ) / 255; - - return this; - - } else if ( size === 6 ) { - - // #ff0000 - this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 1 ), 16 ) / 255; - this.g = parseInt( hex.charAt( 2 ) + hex.charAt( 3 ), 16 ) / 255; - this.b = parseInt( hex.charAt( 4 ) + hex.charAt( 5 ), 16 ) / 255; - - return this; - - } - - } - - if ( style && style.length > 0 ) { - - // color keywords - var hex = ColorKeywords[ style ]; - - if ( hex !== undefined ) { - - // red - this.setHex( hex ); - - } else { - - // unknown color - console.warn( 'THREE.Color: Unknown color ' + style ); - - } - - } - - return this; - - }, - - clone: function () { - - return new this.constructor( this.r, this.g, this.b ); - - }, - - copy: function ( color ) { - - this.r = color.r; - this.g = color.g; - this.b = color.b; - - return this; - - }, - - copyGammaToLinear: function ( color, gammaFactor ) { - - if ( gammaFactor === undefined ) gammaFactor = 2.0; - - this.r = Math.pow( color.r, gammaFactor ); - this.g = Math.pow( color.g, gammaFactor ); - this.b = Math.pow( color.b, gammaFactor ); - - return this; - - }, - - copyLinearToGamma: function ( color, gammaFactor ) { - - if ( gammaFactor === undefined ) gammaFactor = 2.0; - - var safeInverse = ( gammaFactor > 0 ) ? ( 1.0 / gammaFactor ) : 1.0; - - this.r = Math.pow( color.r, safeInverse ); - this.g = Math.pow( color.g, safeInverse ); - this.b = Math.pow( color.b, safeInverse ); - - return this; - - }, - - convertGammaToLinear: function () { - - var r = this.r, g = this.g, b = this.b; - - this.r = r * r; - this.g = g * g; - this.b = b * b; - - return this; - - }, - - convertLinearToGamma: function () { - - this.r = Math.sqrt( this.r ); - this.g = Math.sqrt( this.g ); - this.b = Math.sqrt( this.b ); - - return this; - - }, - - getHex: function () { - - return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0; - - }, - - getHexString: function () { - - return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 ); - - }, - - getHSL: function ( optionalTarget ) { - - // h,s,l ranges are in 0.0 - 1.0 - - var hsl = optionalTarget || { h: 0, s: 0, l: 0 }; - - var r = this.r, g = this.g, b = this.b; - - var max = Math.max( r, g, b ); - var min = Math.min( r, g, b ); - - var hue, saturation; - var lightness = ( min + max ) / 2.0; - - if ( min === max ) { - - hue = 0; - saturation = 0; - - } else { - - var delta = max - min; - - saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); - - switch ( max ) { - - case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; - case g: hue = ( b - r ) / delta + 2; break; - case b: hue = ( r - g ) / delta + 4; break; - - } - - hue /= 6; - - } - - hsl.h = hue; - hsl.s = saturation; - hsl.l = lightness; - - return hsl; - - }, - - getStyle: function () { - - return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')'; - - }, - - offsetHSL: function ( h, s, l ) { - - var hsl = this.getHSL(); - - hsl.h += h; hsl.s += s; hsl.l += l; - - this.setHSL( hsl.h, hsl.s, hsl.l ); - - return this; - - }, - - add: function ( color ) { - - this.r += color.r; - this.g += color.g; - this.b += color.b; - - return this; - - }, - - addColors: function ( color1, color2 ) { - - this.r = color1.r + color2.r; - this.g = color1.g + color2.g; - this.b = color1.b + color2.b; - - return this; - - }, - - addScalar: function ( s ) { - - this.r += s; - this.g += s; - this.b += s; - - return this; - - }, - - sub: function ( color ) { - - this.r = Math.max( 0, this.r - color.r ); - this.g = Math.max( 0, this.g - color.g ); - this.b = Math.max( 0, this.b - color.b ); - - return this; - - }, - - multiply: function ( color ) { - - this.r *= color.r; - this.g *= color.g; - this.b *= color.b; - - return this; - - }, - - multiplyScalar: function ( s ) { - - this.r *= s; - this.g *= s; - this.b *= s; - - return this; - - }, - - lerp: function ( color, alpha ) { - - this.r += ( color.r - this.r ) * alpha; - this.g += ( color.g - this.g ) * alpha; - this.b += ( color.b - this.b ) * alpha; - - return this; - - }, - - equals: function ( c ) { - - return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); - - }, - - fromArray: function ( array, offset ) { - - if ( offset === undefined ) offset = 0; - - this.r = array[ offset ]; - this.g = array[ offset + 1 ]; - this.b = array[ offset + 2 ]; - - return this; - - }, - - toArray: function ( array, offset ) { - - if ( array === undefined ) array = []; - if ( offset === undefined ) offset = 0; - - array[ offset ] = this.r; - array[ offset + 1 ] = this.g; - array[ offset + 2 ] = this.b; - - return array; - - }, - - toJSON: function () { - - return this.getHex(); - - } - -} ); - -/** - * Uniforms library for shared webgl shaders - */ - -var UniformsLib = { - - common: { - - diffuse: { value: new Color( 0xeeeeee ) }, - opacity: { value: 1.0 }, - - map: { value: null }, - uvTransform: { value: new Matrix3() }, - - alphaMap: { value: null }, - - }, - - specularmap: { - - specularMap: { value: null }, - - }, - - envmap: { - - envMap: { value: null }, - flipEnvMap: { value: - 1 }, - reflectivity: { value: 1.0 }, - refractionRatio: { value: 0.98 } - - }, - - aomap: { - - aoMap: { value: null }, - aoMapIntensity: { value: 1 } - - }, - - lightmap: { - - lightMap: { value: null }, - lightMapIntensity: { value: 1 } - - }, - - emissivemap: { - - emissiveMap: { value: null } - - }, - - bumpmap: { - - bumpMap: { value: null }, - bumpScale: { value: 1 } - - }, - - normalmap: { - - normalMap: { value: null }, - normalScale: { value: new Vector2( 1, 1 ) } - - }, - - displacementmap: { - - displacementMap: { value: null }, - displacementScale: { value: 1 }, - displacementBias: { value: 0 } - - }, - - roughnessmap: { - - roughnessMap: { value: null } - - }, - - metalnessmap: { - - metalnessMap: { value: null } - - }, - - gradientmap: { - - gradientMap: { value: null } - - }, - - fog: { - - fogDensity: { value: 0.00025 }, - fogNear: { value: 1 }, - fogFar: { value: 2000 }, - fogColor: { value: new Color( 0xffffff ) } - - }, - - lights: { - - ambientLightColor: { value: [] }, - - directionalLights: { value: [], properties: { - direction: {}, - color: {}, - - shadow: {}, - shadowBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, - - directionalShadowMap: { value: [] }, - directionalShadowMatrix: { value: [] }, - - spotLights: { value: [], properties: { - color: {}, - position: {}, - direction: {}, - distance: {}, - coneCos: {}, - penumbraCos: {}, - decay: {}, - - shadow: {}, - shadowBias: {}, - shadowRadius: {}, - shadowMapSize: {} - } }, - - spotShadowMap: { value: [] }, - spotShadowMatrix: { value: [] }, - - pointLights: { value: [], properties: { - color: {}, - position: {}, - decay: {}, - distance: {}, - - shadow: {}, - shadowBias: {}, - shadowRadius: {}, - shadowMapSize: {}, - shadowCameraNear: {}, - shadowCameraFar: {} - } }, - - pointShadowMap: { value: [] }, - pointShadowMatrix: { value: [] }, - - hemisphereLights: { value: [], properties: { - direction: {}, - skyColor: {}, - groundColor: {} - } }, - - // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src - rectAreaLights: { value: [], properties: { - color: {}, - position: {}, - width: {}, - height: {} - } } - - }, - - points: { - - diffuse: { value: new Color( 0xeeeeee ) }, - opacity: { value: 1.0 }, - size: { value: 1.0 }, - scale: { value: 1.0 }, - map: { value: null }, - uvTransform: { value: new Matrix3() } - - } - -}; - -/** - * Uniform Utilities - */ - -var UniformsUtils = { - - merge: function ( uniforms ) { - - var merged = {}; - - for ( var u = 0; u < uniforms.length; u ++ ) { - - var tmp = this.clone( uniforms[ u ] ); - - for ( var p in tmp ) { - - merged[ p ] = tmp[ p ]; - - } - - } - - return merged; - - }, - - clone: function ( uniforms_src ) { - - var uniforms_dst = {}; - - for ( var u in uniforms_src ) { - - uniforms_dst[ u ] = {}; - - for ( var p in uniforms_src[ u ] ) { - - var parameter_src = uniforms_src[ u ][ p ]; - - if ( parameter_src && ( parameter_src.isColor || - parameter_src.isMatrix3 || parameter_src.isMatrix4 || - parameter_src.isVector2 || parameter_src.isVector3 || parameter_src.isVector4 || - parameter_src.isTexture ) ) { - - uniforms_dst[ u ][ p ] = parameter_src.clone(); - - } else if ( Array.isArray( parameter_src ) ) { - - uniforms_dst[ u ][ p ] = parameter_src.slice(); - - } else { - - uniforms_dst[ u ][ p ] = parameter_src; - - } - - } - - } - - return uniforms_dst; - - } - -}; - -var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\n#endif\n"; - -var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif\n"; - -var alphatest_fragment = "#ifdef ALPHATEST\n\tif ( diffuseColor.a < ALPHATEST ) discard;\n#endif\n"; - -var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( PHYSICAL )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\n\t#endif\n#endif\n"; - -var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; - -var begin_vertex = "\nvec3 transformed = vec3( position );\n"; - -var beginnormal_vertex = "\nvec3 objectNormal = vec3( normal );\n"; - -var bsdfs = "float punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tif( decayExponent > 0.0 ) {\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tfloat maxDistanceCutoffFactor = pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\treturn distanceFalloff * maxDistanceCutoffFactor;\n#else\n\t\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n#endif\n\t}\n\treturn 1.0;\n}\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\n\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\n}\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\treturn 1.0 / ( gl * gv );\n}\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNL = saturate( dot( geometry.normal, incidentLight.direction ) );\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\tfloat D = D_GGX( alpha, dotNH );\n\treturn F * ( G * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat theta = acos( dot( N, V ) );\n\tvec2 uv = vec2(\n\t\tsqrt( saturate( roughness ) ),\n\t\tsaturate( theta / ( 0.5 * PI ) ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.86267 + (0.49788 + 0.01436 * y ) * y;\n\tfloat b = 3.45068 + (4.18814 + y) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = (x > 0.0) ? v : 0.5 * inversesqrt( 1.0 - x * x ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tvec3 result = vec3( LTC_ClippedSphereFormFactor( vectorFormFactor ) );\n\treturn result;\n}\nvec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;\n\treturn specularColor * AB.x + AB.y;\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\n\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\n}\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\n\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\n}\n"; - -var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\n\t\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\n\t\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 );\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif\n"; - -var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; ++ i ) {\n\t\tvec4 plane = clippingPlanes[ i ];\n\t\tif ( dot( vViewPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t\t\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; ++ i ) {\n\t\t\tvec4 plane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vViewPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\tif ( clipped ) discard;\n\t\n\t#endif\n#endif\n"; - -var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\t#if ! defined( PHYSICAL ) && ! defined( PHONG )\n\t\tvarying vec3 vViewPosition;\n\t#endif\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif\n"; - -var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )\n\tvarying vec3 vViewPosition;\n#endif\n"; - -var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n"; - -var color_fragment = "#ifdef USE_COLOR\n\tdiffuseColor.rgb *= vColor;\n#endif"; - -var color_pars_fragment = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif\n"; - -var color_pars_vertex = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif"; - -var color_vertex = "#ifdef USE_COLOR\n\tvColor.xyz = color.xyz;\n#endif"; - -var common = "#define PI 3.14159265359\n#define PI2 6.28318530718\n#define PI_HALF 1.5707963267949\n#define RECIPROCAL_PI 0.31830988618\n#define RECIPROCAL_PI2 0.15915494\n#define LOG2 1.442695\n#define EPSILON 1e-6\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#define whiteCompliment(a) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract(sin(sn) * c);\n}\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\tfloat distance = dot( planeNormal, point - pointOnPlane );\n\treturn - distance * planeNormal + point;\n}\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn sign( dot( point - pointOnPlane, planeNormal ) );\n}\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat linearToRelativeLuminance( const in vec3 color ) {\n\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\n\treturn dot( weights, color.rgb );\n}\n"; - -var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n#define cubeUV_textureSize (1024.0)\nint getFaceFromDirection(vec3 direction) {\n\tvec3 absDirection = abs(direction);\n\tint face = -1;\n\tif( absDirection.x > absDirection.z ) {\n\t\tif(absDirection.x > absDirection.y )\n\t\t\tface = direction.x > 0.0 ? 0 : 3;\n\t\telse\n\t\t\tface = direction.y > 0.0 ? 1 : 4;\n\t}\n\telse {\n\t\tif(absDirection.z > absDirection.y )\n\t\t\tface = direction.z > 0.0 ? 2 : 5;\n\t\telse\n\t\t\tface = direction.y > 0.0 ? 1 : 4;\n\t}\n\treturn face;\n}\n#define cubeUV_maxLods1 (log2(cubeUV_textureSize*0.25) - 1.0)\n#define cubeUV_rangeClamp (exp2((6.0 - 1.0) * 2.0))\nvec2 MipLevelInfo( vec3 vec, float roughnessLevel, float roughness ) {\n\tfloat scale = exp2(cubeUV_maxLods1 - roughnessLevel);\n\tfloat dxRoughness = dFdx(roughness);\n\tfloat dyRoughness = dFdy(roughness);\n\tvec3 dx = dFdx( vec * scale * dxRoughness );\n\tvec3 dy = dFdy( vec * scale * dyRoughness );\n\tfloat d = max( dot( dx, dx ), dot( dy, dy ) );\n\td = clamp(d, 1.0, cubeUV_rangeClamp);\n\tfloat mipLevel = 0.5 * log2(d);\n\treturn vec2(floor(mipLevel), fract(mipLevel));\n}\n#define cubeUV_maxLods2 (log2(cubeUV_textureSize*0.25) - 2.0)\n#define cubeUV_rcpTextureSize (1.0 / cubeUV_textureSize)\nvec2 getCubeUV(vec3 direction, float roughnessLevel, float mipLevel) {\n\tmipLevel = roughnessLevel > cubeUV_maxLods2 - 3.0 ? 0.0 : mipLevel;\n\tfloat a = 16.0 * cubeUV_rcpTextureSize;\n\tvec2 exp2_packed = exp2( vec2( roughnessLevel, mipLevel ) );\n\tvec2 rcp_exp2_packed = vec2( 1.0 ) / exp2_packed;\n\tfloat powScale = exp2_packed.x * exp2_packed.y;\n\tfloat scale = rcp_exp2_packed.x * rcp_exp2_packed.y * 0.25;\n\tfloat mipOffset = 0.75*(1.0 - rcp_exp2_packed.y) * rcp_exp2_packed.x;\n\tbool bRes = mipLevel == 0.0;\n\tscale = bRes && (scale < a) ? a : scale;\n\tvec3 r;\n\tvec2 offset;\n\tint face = getFaceFromDirection(direction);\n\tfloat rcpPowScale = 1.0 / powScale;\n\tif( face == 0) {\n\t\tr = vec3(direction.x, -direction.z, direction.y);\n\t\toffset = vec2(0.0+mipOffset,0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 1) {\n\t\tr = vec3(direction.y, direction.x, direction.z);\n\t\toffset = vec2(scale+mipOffset, 0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 2) {\n\t\tr = vec3(direction.z, direction.x, direction.y);\n\t\toffset = vec2(2.0*scale+mipOffset, 0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 3) {\n\t\tr = vec3(direction.x, direction.z, direction.y);\n\t\toffset = vec2(0.0+mipOffset,0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\telse if( face == 4) {\n\t\tr = vec3(direction.y, direction.x, -direction.z);\n\t\toffset = vec2(scale+mipOffset, 0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\telse {\n\t\tr = vec3(direction.z, -direction.x, direction.y);\n\t\toffset = vec2(2.0*scale+mipOffset, 0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\tr = normalize(r);\n\tfloat texelOffset = 0.5 * cubeUV_rcpTextureSize;\n\tvec2 s = ( r.yz / abs( r.x ) + vec2( 1.0 ) ) * 0.5;\n\tvec2 base = offset + vec2( texelOffset );\n\treturn base + s * ( scale - 2.0 * texelOffset );\n}\n#define cubeUV_maxLods3 (log2(cubeUV_textureSize*0.25) - 3.0)\nvec4 textureCubeUV(vec3 reflectedDirection, float roughness ) {\n\tfloat roughnessVal = roughness* cubeUV_maxLods3;\n\tfloat r1 = floor(roughnessVal);\n\tfloat r2 = r1 + 1.0;\n\tfloat t = fract(roughnessVal);\n\tvec2 mipInfo = MipLevelInfo(reflectedDirection, r1, roughness);\n\tfloat s = mipInfo.y;\n\tfloat level0 = mipInfo.x;\n\tfloat level1 = level0 + 1.0;\n\tlevel1 = level1 > 5.0 ? 5.0 : level1;\n\tlevel0 += min( floor( s + 0.5 ), 5.0 );\n\tvec2 uv_10 = getCubeUV(reflectedDirection, r1, level0);\n\tvec4 color10 = envMapTexelToLinear(texture2D(envMap, uv_10));\n\tvec2 uv_20 = getCubeUV(reflectedDirection, r2, level0);\n\tvec4 color20 = envMapTexelToLinear(texture2D(envMap, uv_20));\n\tvec4 result = mix(color10, color20, t);\n\treturn vec4(result.rgb, 1.0);\n}\n#endif\n"; - -var defaultnormal_vertex = "vec3 transformedNormal = normalMatrix * objectNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n"; - -var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif\n"; - -var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, uv ).x * displacementScale + displacementBias );\n#endif\n"; - -var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif\n"; - -var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif\n"; - -var encodings_fragment = " gl_FragColor = linearToOutputTexel( gl_FragColor );\n"; - -var encodings_pars_fragment = "\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.xyz, vec3( gammaFactor ) ), value.w );\n}\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\n}\nvec4 sRGBToLinear( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.w );\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.w );\n}\nvec4 RGBEToLinear( in vec4 value ) {\n\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\n}\nvec4 LinearToRGBE( in vec4 value ) {\n\tfloat maxComponent = max( max( value.r, value.g ), value.b );\n\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\n\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\n}\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.xyz * value.w * maxRange, 1.0 );\n}\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.x, max( value.g, value.b ) );\n\tfloat M = clamp( maxRGB / maxRange, 0.0, 1.0 );\n\tM = ceil( M * 255.0 ) / 255.0;\n\treturn vec4( value.rgb / ( M * maxRange ), M );\n}\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\n}\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.x, max( value.g, value.b ) );\n\tfloat D = max( maxRange / maxRGB, 1.0 );\n\tD = min( floor( D ) / 255.0, 1.0 );\n\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\n}\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\nvec4 LinearToLogLuv( in vec4 value ) {\n\tvec3 Xp_Y_XYZp = value.rgb * cLogLuvM;\n\tXp_Y_XYZp = max(Xp_Y_XYZp, vec3(1e-6, 1e-6, 1e-6));\n\tvec4 vResult;\n\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\n\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\n\tvResult.w = fract(Le);\n\tvResult.z = (Le - (floor(vResult.w*255.0))/255.0)/255.0;\n\treturn vResult;\n}\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\nvec4 LogLuvToLinear( in vec4 value ) {\n\tfloat Le = value.z * 255.0 + value.w;\n\tvec3 Xp_Y_XYZp;\n\tXp_Y_XYZp.y = exp2((Le - 127.0) / 2.0);\n\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\n\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\n\tvec3 vRGB = Xp_Y_XYZp.rgb * cLogLuvInverseM;\n\treturn vec4( max(vRGB, 0.0), 1.0 );\n}\n"; - -var envmap_fragment = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\tvec2 sampleUV;\n\t\treflectVec = normalize( reflectVec );\n\t\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\t\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\tvec4 envColor = texture2D( envMap, sampleUV );\n\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\treflectVec = normalize( reflectVec );\n\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0, 0.0, 1.0 ) );\n\t\tvec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\tenvColor = envMapTexelToLinear( envColor );\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif\n"; - -var envmap_pars_fragment = "#if defined( USE_ENVMAP ) || defined( PHYSICAL )\n\tuniform float reflectivity;\n\tuniform float envMapIntensity;\n#endif\n#ifdef USE_ENVMAP\n\t#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) )\n\t\tvarying vec3 vWorldPosition;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\tuniform float flipEnvMap;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif\n"; - -var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif\n"; - -var envmap_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif\n"; - -var fog_vertex = "\n#ifdef USE_FOG\nfogDepth = -mvPosition.z;\n#endif"; - -var fog_pars_vertex = "#ifdef USE_FOG\n varying float fogDepth;\n#endif\n"; - -var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = whiteCompliment( exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 ) );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif\n"; - -var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float fogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif\n"; - -var gradientmap_pars_fragment = "#ifdef TOON\n\tuniform sampler2D gradientMap;\n\tvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\t\tfloat dotNL = dot( normal, lightDirection );\n\t\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t\t#ifdef USE_GRADIENTMAP\n\t\t\treturn texture2D( gradientMap, coord ).rgb;\n\t\t#else\n\t\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t\t#endif\n\t}\n#endif\n"; - -var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\treflectedLight.indirectDiffuse += PI * texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n#endif\n"; - -var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; - -var lights_lambert_vertex = "vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\n#if NUM_POINT_LIGHTS > 0\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_DIR_LIGHTS > 0\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvLightFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n#endif\n"; - -var lights_pars = "uniform vec3 ambientLightColor;\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treturn irradiance;\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tdirectLight.color = directionalLight.color;\n\t\tdirectLight.direction = directionalLight.direction;\n\t\tdirectLight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t\tfloat shadowCameraNear;\n\t\tfloat shadowCameraFar;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tdirectLight.color = pointLight.color;\n\t\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\n\t\tdirectLight.visible = ( directLight.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tfloat angleCos = dot( directLight.direction, spotLight.direction );\n\t\tif ( angleCos > spotLight.coneCos ) {\n\t\t\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\t\tdirectLight.color = spotLight.color;\n\t\t\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tdirectLight.visible = true;\n\t\t} else {\n\t\t\tdirectLight.color = vec3( 0.0 );\n\t\t\tdirectLight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltcMat;\tuniform sampler2D ltcMag;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\n\t\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tirradiance *= PI;\n\t\t#endif\n\t\treturn irradiance;\n\t}\n#endif\n#if defined( USE_ENVMAP ) && defined( PHYSICAL )\n\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\n\t\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\tvec4 envMapColor = textureCubeUV( queryVec, 1.0 );\n\t\t#else\n\t\t\tvec4 envMapColor = vec4( 0.0 );\n\t\t#endif\n\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t}\n\tfloat getSpecularMIPLevel( const in float blinnShininessExponent, const in int maxMIPLevel ) {\n\t\tfloat maxMIPLevelScalar = float( maxMIPLevel );\n\t\tfloat desiredMIPLevel = maxMIPLevelScalar + 0.79248 - 0.5 * log2( pow2( blinnShininessExponent ) + 1.0 );\n\t\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\n\t}\n\tvec3 getLightProbeIndirectRadiance( const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) {\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( -geometry.viewDir, geometry.normal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( -geometry.viewDir, geometry.normal, refractionRatio );\n\t\t#endif\n\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\tfloat specularMIPLevel = getSpecularMIPLevel( blinnShininessExponent, maxMIPLevel );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\tvec4 envMapColor = textureCubeUV(queryReflectVec, BlinnExponentToGGXRoughness(blinnShininessExponent));\n\t\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\t\tvec2 sampleUV;\n\t\t\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\t\t\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, sampleUV, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, sampleUV, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0,0.0,1.0 ) );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#endif\n\t\treturn envMapColor.rgb * envMapIntensity;\n\t}\n#endif\n"; - -var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;\n"; - -var lights_phong_pars_fragment = "varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct BlinnPhongMaterial {\n\tvec3\tdiffuseColor;\n\tvec3\tspecularColor;\n\tfloat\tspecularShininess;\n\tfloat\tspecularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\t#ifdef TOON\n\t\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\t#else\n\t\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\t\tvec3 irradiance = dotNL * directLight.color;\n\t#endif\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material )\t(0)\n"; - -var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nmaterial.specularRoughness = clamp( roughnessFactor, 0.04, 1.0 );\n#ifdef STANDARD\n\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.clearCoat = saturate( clearCoat );\tmaterial.clearCoatRoughness = clamp( clearCoatRoughness, 0.04, 1.0 );\n#endif\n"; - -var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3\tdiffuseColor;\n\tfloat\tspecularRoughness;\n\tvec3\tspecularColor;\n\t#ifndef STANDARD\n\t\tfloat clearCoat;\n\t\tfloat clearCoatRoughness;\n\t#endif\n};\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\nfloat clearCoatDHRApprox( const in float roughness, const in float dotNL ) {\n\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.specularRoughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos - halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos + halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos + halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos - halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tfloat norm = texture2D( ltcMag, uv ).a;\n\t\tvec4 t = texture2D( ltcMat, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( 1, 0, t.y ),\n\t\t\tvec3( 0, t.z, 0 ),\n\t\t\tvec3( t.w, 0, t.x )\n\t\t);\n\t\treflectedLight.directSpecular += lightColor * material.specularColor * norm * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\t#ifndef STANDARD\n\t\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\n\t#else\n\t\tfloat clearCoatDHR = 0.0;\n\t#endif\n\treflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry, material.specularColor, material.specularRoughness );\n\treflectedLight.directDiffuse += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\t#ifndef STANDARD\n\t\treflectedLight.directSpecular += irradiance * material.clearCoat * BRDF_Specular_GGX( directLight, geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\n\t#endif\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 clearCoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t#ifndef STANDARD\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\tfloat dotNL = dotNV;\n\t\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\n\t#else\n\t\tfloat clearCoatDHR = 0.0;\n\t#endif\n\treflectedLight.indirectSpecular += ( 1.0 - clearCoatDHR ) * radiance * BRDF_Specular_GGX_Environment( geometry, material.specularColor, material.specularRoughness );\n\t#ifndef STANDARD\n\t\treflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\n\t#endif\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\n#define Material_BlinnShininessExponent( material ) GGXRoughnessToBlinnExponent( material.specularRoughness )\n#define Material_ClearCoat_BlinnShininessExponent( material ) GGXRoughnessToBlinnExponent( material.clearCoatRoughness )\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}\n"; - -var lights_template = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = normalize( vViewPosition );\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( pointLight.shadow, directLight.visible ) ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( spotLight.shadow, directLight.visible ) ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( directionalLight.shadow, directLight.visible ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#ifdef USE_LIGHTMAP\n\t\tvec3 lightMapIrradiance = texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tlightMapIrradiance *= PI;\n\t\t#endif\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t}\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( PHYSICAL ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tirradiance += getLightProbeIndirectIrradiance( geometry, 8 );\n\t#endif\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tvec3 radiance = getLightProbeIndirectRadiance( geometry, Material_BlinnShininessExponent( material ), 8 );\n\t#ifndef STANDARD\n\t\tvec3 clearCoatRadiance = getLightProbeIndirectRadiance( geometry, Material_ClearCoat_BlinnShininessExponent( material ), 8 );\n\t#else\n\t\tvec3 clearCoatRadiance = vec3( 0.0 );\n\t#endif\n\tRE_IndirectSpecular( radiance, clearCoatRadiance, geometry, material, reflectedLight );\n#endif\n"; - -var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; - -var logdepthbuf_pars_fragment = "#ifdef USE_LOGDEPTHBUF\n\tuniform float logDepthBufFC;\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t#endif\n#endif\n"; - -var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t#endif\n\tuniform float logDepthBufFC;\n#endif"; - -var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t#else\n\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\tgl_Position.z *= gl_Position.w;\n\t#endif\n#endif\n"; - -var map_fragment = "#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif\n"; - -var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n"; - -var map_particle_fragment = "#ifdef USE_MAP\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif\n"; - -var map_particle_pars_fragment = "#ifdef USE_MAP\n\tuniform mat3 uvTransform;\n\tuniform sampler2D map;\n#endif\n"; - -var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif\n"; - -var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; - -var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\n\tobjectNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\n\tobjectNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\n\tobjectNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\n#endif\n"; - -var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\t#ifndef USE_MORPHNORMALS\n\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif"; - -var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\n\ttransformed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\n\ttransformed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\n\ttransformed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\ttransformed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\n\ttransformed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\n\ttransformed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\n\ttransformed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n\t#endif\n#endif\n"; - -var normal_fragment = "#ifdef FLAT_SHADED\n\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n#endif\n#ifdef USE_NORMALMAP\n\tnormal = perturbNormal2Arb( -vViewPosition, normal );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif\n"; - -var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\n\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\t\tvec3 S = normalize( q0 * st1.t - q1 * st0.t );\n\t\tvec3 T = normalize( -q0 * st1.s + q1 * st0.s );\n\t\tvec3 N = normalize( surf_norm );\n\t\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t\tmapN.xy = normalScale * mapN.xy;\n\t\tmat3 tsn = mat3( S, T, N );\n\t\treturn normalize( tsn * mapN );\n\t}\n#endif\n"; - -var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn (( near + viewZ ) * far ) / (( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}\n"; - -var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif\n"; - -var project_vertex = "vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );\ngl_Position = projectionMatrix * mvPosition;\n"; - -var dithering_fragment = "#if defined( DITHERING )\n gl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif\n"; - -var dithering_pars_fragment = "#if defined( DITHERING )\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif\n"; - -var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif\n"; - -var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; - -var shadowmap_pars_fragment = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHTS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHTS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tfloat texture2DShadowLerp( sampler2D depths, vec2 size, vec2 uv, float compare ) {\n\t\tconst vec2 offset = vec2( 0.0, 1.0 );\n\t\tvec2 texelSize = vec2( 1.0 ) / size;\n\t\tvec2 centroidUV = floor( uv * size + 0.5 ) / size;\n\t\tfloat lb = texture2DCompare( depths, centroidUV + texelSize * offset.xx, compare );\n\t\tfloat lt = texture2DCompare( depths, centroidUV + texelSize * offset.xy, compare );\n\t\tfloat rb = texture2DCompare( depths, centroidUV + texelSize * offset.yx, compare );\n\t\tfloat rt = texture2DCompare( depths, centroidUV + texelSize * offset.yy, compare );\n\t\tvec2 f = fract( uv * size + 0.5 );\n\t\tfloat a = mix( lb, lt, f.y );\n\t\tfloat b = mix( rb, rt, f.y );\n\t\tfloat c = mix( a, b, f.x );\n\t\treturn c;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\t\tbool frustumTest = all( frustumTestVec );\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tshadow = (\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif\n"; - -var shadowmap_pars_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHTS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHTS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHTS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\n\t#endif\n#endif\n"; - -var shadowmap_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n#endif\n"; - -var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\tDirectionalLight directionalLight;\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tshadow *= bool( directionalLight.shadow ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\tSpotLight spotLight;\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tshadow *= bool( spotLight.shadow ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\tPointLight pointLight;\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tshadow *= bool( pointLight.shadow ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#endif\n\t#endif\n\treturn shadow;\n}\n"; - -var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; - -var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform sampler2D boneTexture;\n\t\tuniform int boneTextureSize;\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tfloat j = i * 4.0;\n\t\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\t\ty = dy * ( y + 0.5 );\n\t\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\t\treturn bone;\n\t\t}\n\t#else\n\t\tuniform mat4 boneMatrices[ MAX_BONES ];\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tmat4 bone = boneMatrices[ int(i) ];\n\t\t\treturn bone;\n\t\t}\n\t#endif\n#endif\n"; - -var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif\n"; - -var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n#endif\n"; - -var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; - -var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; - -var tonemapping_fragment = "#if defined( TONE_MAPPING )\n gl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif\n"; - -var tonemapping_pars_fragment = "#ifndef saturate\n\t#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nuniform float toneMappingWhitePoint;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\n#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )\nvec3 Uncharted2ToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\n"; - -var uv_pars_fragment = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvarying vec2 vUv;\n#endif"; - -var uv_pars_vertex = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\n"; - -var uv_vertex = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif"; - -var uv2_pars_fragment = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif"; - -var uv2_pars_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n#endif"; - -var uv2_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = uv2;\n#endif"; - -var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\n\tvec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );\n#endif\n"; - -var cube_frag = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldPosition;\nvoid main() {\n\tgl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );\n\tgl_FragColor.a *= opacity;\n}\n"; - -var cube_vert = "varying vec3 vWorldPosition;\n#include \nvoid main() {\n\tvWorldPosition = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}\n"; - -var depth_frag = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( gl_FragCoord.z ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( gl_FragCoord.z );\n\t#endif\n}\n"; - -var depth_vert = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var distanceRGBA_frag = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}\n"; - -var distanceRGBA_vert = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}\n"; - -var equirect_frag = "uniform sampler2D tEquirect;\nvarying vec3 vWorldPosition;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldPosition );\n\tvec2 sampleUV;\n\tsampleUV.y = asin( clamp( direction.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\tsampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n}\n"; - -var equirect_vert = "varying vec3 vWorldPosition;\n#include \nvoid main() {\n\tvWorldPosition = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}\n"; - -var linedashed_frag = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var linedashed_vert = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvLineDistance = scale * lineDistance;\n\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}\n"; - -var meshbasic_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\treflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var meshbasic_vert = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_ENVMAP\n\t#include \n\t#include \n\t#include \n\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var meshlambert_frag = "uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\treflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );\n\t#include \n\treflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n\t#else\n\t\treflectedLight.directDiffuse = vLightFront;\n\t#endif\n\treflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var meshlambert_vert = "#define LAMBERT\nvarying vec3 vLightFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var meshphong_frag = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var meshphong_vert = "#define PHONG\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var meshphysical_frag = "#define PHYSICAL\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifndef STANDARD\n\tuniform float clearCoat;\n\tuniform float clearCoatRoughness;\n#endif\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var meshphysical_vert = "#define PHYSICAL\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}\n"; - -var normal_frag = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n}\n"; - -var normal_vert = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}\n"; - -var points_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var points_vert = "uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#ifdef USE_SIZEATTENUATION\n\t\tgl_PointSize = size * ( scale / - mvPosition.z );\n\t#else\n\t\tgl_PointSize = size;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var shadow_frag = "uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n}\n"; - -var shadow_vert = "#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; - -var ShaderChunk = { - alphamap_fragment: alphamap_fragment, - alphamap_pars_fragment: alphamap_pars_fragment, - alphatest_fragment: alphatest_fragment, - aomap_fragment: aomap_fragment, - aomap_pars_fragment: aomap_pars_fragment, - begin_vertex: begin_vertex, - beginnormal_vertex: beginnormal_vertex, - bsdfs: bsdfs, - bumpmap_pars_fragment: bumpmap_pars_fragment, - clipping_planes_fragment: clipping_planes_fragment, - clipping_planes_pars_fragment: clipping_planes_pars_fragment, - clipping_planes_pars_vertex: clipping_planes_pars_vertex, - clipping_planes_vertex: clipping_planes_vertex, - color_fragment: color_fragment, - color_pars_fragment: color_pars_fragment, - color_pars_vertex: color_pars_vertex, - color_vertex: color_vertex, - common: common, - cube_uv_reflection_fragment: cube_uv_reflection_fragment, - defaultnormal_vertex: defaultnormal_vertex, - displacementmap_pars_vertex: displacementmap_pars_vertex, - displacementmap_vertex: displacementmap_vertex, - emissivemap_fragment: emissivemap_fragment, - emissivemap_pars_fragment: emissivemap_pars_fragment, - encodings_fragment: encodings_fragment, - encodings_pars_fragment: encodings_pars_fragment, - envmap_fragment: envmap_fragment, - envmap_pars_fragment: envmap_pars_fragment, - envmap_pars_vertex: envmap_pars_vertex, - envmap_vertex: envmap_vertex, - fog_vertex: fog_vertex, - fog_pars_vertex: fog_pars_vertex, - fog_fragment: fog_fragment, - fog_pars_fragment: fog_pars_fragment, - gradientmap_pars_fragment: gradientmap_pars_fragment, - lightmap_fragment: lightmap_fragment, - lightmap_pars_fragment: lightmap_pars_fragment, - lights_lambert_vertex: lights_lambert_vertex, - lights_pars: lights_pars, - lights_phong_fragment: lights_phong_fragment, - lights_phong_pars_fragment: lights_phong_pars_fragment, - lights_physical_fragment: lights_physical_fragment, - lights_physical_pars_fragment: lights_physical_pars_fragment, - lights_template: lights_template, - logdepthbuf_fragment: logdepthbuf_fragment, - logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, - logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, - logdepthbuf_vertex: logdepthbuf_vertex, - map_fragment: map_fragment, - map_pars_fragment: map_pars_fragment, - map_particle_fragment: map_particle_fragment, - map_particle_pars_fragment: map_particle_pars_fragment, - metalnessmap_fragment: metalnessmap_fragment, - metalnessmap_pars_fragment: metalnessmap_pars_fragment, - morphnormal_vertex: morphnormal_vertex, - morphtarget_pars_vertex: morphtarget_pars_vertex, - morphtarget_vertex: morphtarget_vertex, - normal_fragment: normal_fragment, - normalmap_pars_fragment: normalmap_pars_fragment, - packing: packing, - premultiplied_alpha_fragment: premultiplied_alpha_fragment, - project_vertex: project_vertex, - dithering_fragment: dithering_fragment, - dithering_pars_fragment: dithering_pars_fragment, - roughnessmap_fragment: roughnessmap_fragment, - roughnessmap_pars_fragment: roughnessmap_pars_fragment, - shadowmap_pars_fragment: shadowmap_pars_fragment, - shadowmap_pars_vertex: shadowmap_pars_vertex, - shadowmap_vertex: shadowmap_vertex, - shadowmask_pars_fragment: shadowmask_pars_fragment, - skinbase_vertex: skinbase_vertex, - skinning_pars_vertex: skinning_pars_vertex, - skinning_vertex: skinning_vertex, - skinnormal_vertex: skinnormal_vertex, - specularmap_fragment: specularmap_fragment, - specularmap_pars_fragment: specularmap_pars_fragment, - tonemapping_fragment: tonemapping_fragment, - tonemapping_pars_fragment: tonemapping_pars_fragment, - uv_pars_fragment: uv_pars_fragment, - uv_pars_vertex: uv_pars_vertex, - uv_vertex: uv_vertex, - uv2_pars_fragment: uv2_pars_fragment, - uv2_pars_vertex: uv2_pars_vertex, - uv2_vertex: uv2_vertex, - worldpos_vertex: worldpos_vertex, - - cube_frag: cube_frag, - cube_vert: cube_vert, - depth_frag: depth_frag, - depth_vert: depth_vert, - distanceRGBA_frag: distanceRGBA_frag, - distanceRGBA_vert: distanceRGBA_vert, - equirect_frag: equirect_frag, - equirect_vert: equirect_vert, - linedashed_frag: linedashed_frag, - linedashed_vert: linedashed_vert, - meshbasic_frag: meshbasic_frag, - meshbasic_vert: meshbasic_vert, - meshlambert_frag: meshlambert_frag, - meshlambert_vert: meshlambert_vert, - meshphong_frag: meshphong_frag, - meshphong_vert: meshphong_vert, - meshphysical_frag: meshphysical_frag, - meshphysical_vert: meshphysical_vert, - normal_frag: normal_frag, - normal_vert: normal_vert, - points_frag: points_frag, - points_vert: points_vert, - shadow_frag: shadow_frag, - shadow_vert: shadow_vert -}; - -/** - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - * @author mikael emtinger / http://gomo.se/ - */ - -var ShaderLib = { - - basic: { - - uniforms: UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.meshbasic_vert, - fragmentShader: ShaderChunk.meshbasic_frag - - }, - - lambert: { - - uniforms: UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: new Color( 0x000000 ) } - } - ] ), - - vertexShader: ShaderChunk.meshlambert_vert, - fragmentShader: ShaderChunk.meshlambert_frag - - }, - - phong: { - - uniforms: UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.specularmap, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.gradientmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: new Color( 0x000000 ) }, - specular: { value: new Color( 0x111111 ) }, - shininess: { value: 30 } - } - ] ), - - vertexShader: ShaderChunk.meshphong_vert, - fragmentShader: ShaderChunk.meshphong_frag - - }, - - standard: { - - uniforms: UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.envmap, - UniformsLib.aomap, - UniformsLib.lightmap, - UniformsLib.emissivemap, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - UniformsLib.roughnessmap, - UniformsLib.metalnessmap, - UniformsLib.fog, - UniformsLib.lights, - { - emissive: { value: new Color( 0x000000 ) }, - roughness: { value: 0.5 }, - metalness: { value: 0.5 }, - envMapIntensity: { value: 1 } // temporary - } - ] ), - - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag - - }, - - points: { - - uniforms: UniformsUtils.merge( [ - UniformsLib.points, - UniformsLib.fog - ] ), - - vertexShader: ShaderChunk.points_vert, - fragmentShader: ShaderChunk.points_frag - - }, - - dashed: { - - uniforms: UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.fog, - { - scale: { value: 1 }, - dashSize: { value: 1 }, - totalSize: { value: 2 } - } - ] ), - - vertexShader: ShaderChunk.linedashed_vert, - fragmentShader: ShaderChunk.linedashed_frag - - }, - - depth: { - - uniforms: UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.displacementmap - ] ), - - vertexShader: ShaderChunk.depth_vert, - fragmentShader: ShaderChunk.depth_frag - - }, - - normal: { - - uniforms: UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.bumpmap, - UniformsLib.normalmap, - UniformsLib.displacementmap, - { - opacity: { value: 1.0 } - } - ] ), - - vertexShader: ShaderChunk.normal_vert, - fragmentShader: ShaderChunk.normal_frag - - }, - - /* ------------------------------------------------------------------------- - // Cube map shader - ------------------------------------------------------------------------- */ - - cube: { - - uniforms: { - tCube: { value: null }, - tFlip: { value: - 1 }, - opacity: { value: 1.0 } - }, - - vertexShader: ShaderChunk.cube_vert, - fragmentShader: ShaderChunk.cube_frag - - }, - - equirect: { - - uniforms: { - tEquirect: { value: null }, - }, - - vertexShader: ShaderChunk.equirect_vert, - fragmentShader: ShaderChunk.equirect_frag - - }, - - distanceRGBA: { - - uniforms: UniformsUtils.merge( [ - UniformsLib.common, - UniformsLib.displacementmap, - { - referencePosition: { value: new Vector3() }, - nearDistance: { value: 1 }, - farDistance: { value: 1000 } - } - ] ), - - vertexShader: ShaderChunk.distanceRGBA_vert, - fragmentShader: ShaderChunk.distanceRGBA_frag - - }, - - shadow: { - - uniforms: UniformsUtils.merge( [ - UniformsLib.lights, - UniformsLib.fog, - { - color: { value: new Color( 0x00000 ) }, - opacity: { value: 1.0 } - }, - ] ), - - vertexShader: ShaderChunk.shadow_vert, - fragmentShader: ShaderChunk.shadow_frag - - } - -}; - -ShaderLib.physical = { - - uniforms: UniformsUtils.merge( [ - ShaderLib.standard.uniforms, - { - clearCoat: { value: 0 }, - clearCoatRoughness: { value: 0 } - } - ] ), - - vertexShader: ShaderChunk.meshphysical_vert, - fragmentShader: ShaderChunk.meshphysical_frag - -}; - -/** - * @author bhouston / http://clara.io - */ - -function Box2( min, max ) { - - this.min = ( min !== undefined ) ? min : new Vector2( + Infinity, + Infinity ); - this.max = ( max !== undefined ) ? max : new Vector2( - Infinity, - Infinity ); - -} - -Object.assign( Box2.prototype, { - - set: function ( min, max ) { - - this.min.copy( min ); - this.max.copy( max ); - - return this; - - }, - - setFromPoints: function ( points ) { - - this.makeEmpty(); - - for ( var i = 0, il = points.length; i < il; i ++ ) { - - this.expandByPoint( points[ i ] ); - - } - - return this; - - }, - - setFromCenterAndSize: function () { - - var v1 = new Vector2(); - - return function setFromCenterAndSize( center, size ) { - - var halfSize = v1.copy( size ).multiplyScalar( 0.5 ); - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); - - return this; - - }; - - }(), - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( box ) { - - this.min.copy( box.min ); - this.max.copy( box.max ); - - return this; - - }, - - makeEmpty: function () { - - this.min.x = this.min.y = + Infinity; - this.max.x = this.max.y = - Infinity; - - return this; - - }, - - isEmpty: function () { - - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); - - }, - - getCenter: function ( optionalTarget ) { - - var result = optionalTarget || new Vector2(); - return this.isEmpty() ? result.set( 0, 0 ) : result.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - - }, - - getSize: function ( optionalTarget ) { - - var result = optionalTarget || new Vector2(); - return this.isEmpty() ? result.set( 0, 0 ) : result.subVectors( this.max, this.min ); - - }, - - expandByPoint: function ( point ) { - - this.min.min( point ); - this.max.max( point ); - - return this; - - }, - - expandByVector: function ( vector ) { - - this.min.sub( vector ); - this.max.add( vector ); - - return this; - - }, - - expandByScalar: function ( scalar ) { - - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); - - return this; - - }, - - containsPoint: function ( point ) { - - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y ? false : true; - - }, - - containsBox: function ( box ) { - - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y; - - }, - - getParameter: function ( point, optionalTarget ) { - - // This can potentially have a divide by zero if the box - // has a size dimension of 0. - - var result = optionalTarget || new Vector2(); - - return result.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ) - ); - - }, - - intersectsBox: function ( box ) { - - // using 4 splitting planes to rule out intersections - - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y ? false : true; - - }, - - clampPoint: function ( point, optionalTarget ) { - - var result = optionalTarget || new Vector2(); - return result.copy( point ).clamp( this.min, this.max ); - - }, - - distanceToPoint: function () { - - var v1 = new Vector2(); - - return function distanceToPoint( point ) { - - var clampedPoint = v1.copy( point ).clamp( this.min, this.max ); - return clampedPoint.sub( point ).length(); - - }; - - }(), - - intersect: function ( box ) { - - this.min.max( box.min ); - this.max.min( box.max ); - - return this; - - }, - - union: function ( box ) { - - this.min.min( box.min ); - this.max.max( box.max ); - - return this; - - }, - - translate: function ( offset ) { - - this.min.add( offset ); - this.max.add( offset ); - - return this; - - }, - - equals: function ( box ) { - - return box.min.equals( this.min ) && box.max.equals( this.max ); - - } - -} ); - -/** - * @author mikael emtinger / http://gomo.se/ - * @author alteredq / http://alteredqualia.com/ - */ - -function WebGLFlareRenderer( renderer, gl, state, textures, capabilities ) { - - var vertexBuffer, elementBuffer; - var shader, program, attributes, uniforms; - - var tempTexture, occlusionTexture; - - function init() { - - var vertices = new Float32Array( [ - - 1, - 1, 0, 0, - 1, - 1, 1, 0, - 1, 1, 1, 1, - - 1, 1, 0, 1 - ] ); - - var faces = new Uint16Array( [ - 0, 1, 2, - 0, 2, 3 - ] ); - - // buffers - - vertexBuffer = gl.createBuffer(); - elementBuffer = gl.createBuffer(); - - gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer ); - gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW ); - - gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); - gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, faces, gl.STATIC_DRAW ); - - // textures - - tempTexture = gl.createTexture(); - occlusionTexture = gl.createTexture(); - - state.bindTexture( gl.TEXTURE_2D, tempTexture ); - gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGB, 16, 16, 0, gl.RGB, gl.UNSIGNED_BYTE, null ); - gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE ); - gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE ); - gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); - - state.bindTexture( gl.TEXTURE_2D, occlusionTexture ); - gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, 16, 16, 0, gl.RGBA, gl.UNSIGNED_BYTE, null ); - gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE ); - gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE ); - gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); - - shader = { - - vertexShader: [ - - 'uniform lowp int renderType;', - - 'uniform vec3 screenPosition;', - 'uniform vec2 scale;', - 'uniform float rotation;', - - 'uniform sampler2D occlusionMap;', - - 'attribute vec2 position;', - 'attribute vec2 uv;', - - 'varying vec2 vUV;', - 'varying float vVisibility;', - - 'void main() {', - - ' vUV = uv;', - - ' vec2 pos = position;', - - ' if ( renderType == 2 ) {', - - ' vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) );', - ' visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) );', - ' visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) );', - ' visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) );', - ' visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) );', - ' visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) );', - ' visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) );', - ' visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) );', - ' visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) );', - - ' vVisibility = visibility.r / 9.0;', - ' vVisibility *= 1.0 - visibility.g / 9.0;', - ' vVisibility *= visibility.b / 9.0;', - ' vVisibility *= 1.0 - visibility.a / 9.0;', - - ' pos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;', - ' pos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;', - - ' }', - - ' gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );', - - '}' - - ].join( '\n' ), - - fragmentShader: [ - - 'uniform lowp int renderType;', - - 'uniform sampler2D map;', - 'uniform float opacity;', - 'uniform vec3 color;', - - 'varying vec2 vUV;', - 'varying float vVisibility;', - - 'void main() {', - - // pink square - - ' if ( renderType == 0 ) {', - - ' gl_FragColor = vec4( 1.0, 0.0, 1.0, 0.0 );', - - // restore - - ' } else if ( renderType == 1 ) {', - - ' gl_FragColor = texture2D( map, vUV );', - - // flare - - ' } else {', - - ' vec4 texture = texture2D( map, vUV );', - ' texture.a *= opacity * vVisibility;', - ' gl_FragColor = texture;', - ' gl_FragColor.rgb *= color;', - - ' }', - - '}' - - ].join( '\n' ) - - }; - - program = createProgram( shader ); - - attributes = { - vertex: gl.getAttribLocation( program, 'position' ), - uv: gl.getAttribLocation( program, 'uv' ) - }; - - uniforms = { - renderType: gl.getUniformLocation( program, 'renderType' ), - map: gl.getUniformLocation( program, 'map' ), - occlusionMap: gl.getUniformLocation( program, 'occlusionMap' ), - opacity: gl.getUniformLocation( program, 'opacity' ), - color: gl.getUniformLocation( program, 'color' ), - scale: gl.getUniformLocation( program, 'scale' ), - rotation: gl.getUniformLocation( program, 'rotation' ), - screenPosition: gl.getUniformLocation( program, 'screenPosition' ) - }; - - } - - /* - * Render lens flares - * Method: renders 16x16 0xff00ff-colored points scattered over the light source area, - * reads these back and calculates occlusion. - */ - - this.render = function ( flares, scene, camera, viewport ) { - - if ( flares.length === 0 ) return; - - var tempPosition = new Vector3(); - - var invAspect = viewport.w / viewport.z, - halfViewportWidth = viewport.z * 0.5, - halfViewportHeight = viewport.w * 0.5; - - var size = 16 / viewport.w, - scale = new Vector2( size * invAspect, size ); - - var screenPosition = new Vector3( 1, 1, 0 ), - screenPositionPixels = new Vector2( 1, 1 ); - - var validArea = new Box2(); - - validArea.min.set( viewport.x, viewport.y ); - validArea.max.set( viewport.x + ( viewport.z - 16 ), viewport.y + ( viewport.w - 16 ) ); - - if ( program === undefined ) { - - init(); - - } - - state.useProgram( program ); - - state.initAttributes(); - state.enableAttribute( attributes.vertex ); - state.enableAttribute( attributes.uv ); - state.disableUnusedAttributes(); - - // loop through all lens flares to update their occlusion and positions - // setup gl and common used attribs/uniforms - - gl.uniform1i( uniforms.occlusionMap, 0 ); - gl.uniform1i( uniforms.map, 1 ); - - gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer ); - gl.vertexAttribPointer( attributes.vertex, 2, gl.FLOAT, false, 2 * 8, 0 ); - gl.vertexAttribPointer( attributes.uv, 2, gl.FLOAT, false, 2 * 8, 8 ); - - gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); - - state.disable( gl.CULL_FACE ); - state.buffers.depth.setMask( false ); - - for ( var i = 0, l = flares.length; i < l; i ++ ) { - - size = 16 / viewport.w; - scale.set( size * invAspect, size ); - - // calc object screen position - - var flare = flares[ i ]; - - tempPosition.set( flare.matrixWorld.elements[ 12 ], flare.matrixWorld.elements[ 13 ], flare.matrixWorld.elements[ 14 ] ); - - tempPosition.applyMatrix4( camera.matrixWorldInverse ); - tempPosition.applyMatrix4( camera.projectionMatrix ); - - // setup arrays for gl programs - - screenPosition.copy( tempPosition ); - - // horizontal and vertical coordinate of the lower left corner of the pixels to copy - - screenPositionPixels.x = viewport.x + ( screenPosition.x * halfViewportWidth ) + halfViewportWidth - 8; - screenPositionPixels.y = viewport.y + ( screenPosition.y * halfViewportHeight ) + halfViewportHeight - 8; - - // screen cull - - if ( validArea.containsPoint( screenPositionPixels ) === true ) { - - // save current RGB to temp texture - - state.activeTexture( gl.TEXTURE0 ); - state.bindTexture( gl.TEXTURE_2D, null ); - state.activeTexture( gl.TEXTURE1 ); - state.bindTexture( gl.TEXTURE_2D, tempTexture ); - gl.copyTexImage2D( gl.TEXTURE_2D, 0, gl.RGB, screenPositionPixels.x, screenPositionPixels.y, 16, 16, 0 ); - - - // render pink quad - - gl.uniform1i( uniforms.renderType, 0 ); - gl.uniform2f( uniforms.scale, scale.x, scale.y ); - gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z ); - - state.disable( gl.BLEND ); - state.enable( gl.DEPTH_TEST ); - - gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - - // copy result to occlusionMap - - state.activeTexture( gl.TEXTURE0 ); - state.bindTexture( gl.TEXTURE_2D, occlusionTexture ); - gl.copyTexImage2D( gl.TEXTURE_2D, 0, gl.RGBA, screenPositionPixels.x, screenPositionPixels.y, 16, 16, 0 ); - - - // restore graphics - - gl.uniform1i( uniforms.renderType, 1 ); - state.disable( gl.DEPTH_TEST ); - - state.activeTexture( gl.TEXTURE1 ); - state.bindTexture( gl.TEXTURE_2D, tempTexture ); - gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - - // update object positions - - flare.positionScreen.copy( screenPosition ); - - if ( flare.customUpdateCallback ) { - - flare.customUpdateCallback( flare ); - - } else { - - flare.updateLensFlares(); - - } - - // render flares - - gl.uniform1i( uniforms.renderType, 2 ); - state.enable( gl.BLEND ); - - for ( var j = 0, jl = flare.lensFlares.length; j < jl; j ++ ) { - - var sprite = flare.lensFlares[ j ]; - - if ( sprite.opacity > 0.001 && sprite.scale > 0.001 ) { - - screenPosition.x = sprite.x; - screenPosition.y = sprite.y; - screenPosition.z = sprite.z; - - size = sprite.size * sprite.scale / viewport.w; - - scale.x = size * invAspect; - scale.y = size; - - gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z ); - gl.uniform2f( uniforms.scale, scale.x, scale.y ); - gl.uniform1f( uniforms.rotation, sprite.rotation ); - - gl.uniform1f( uniforms.opacity, sprite.opacity ); - gl.uniform3f( uniforms.color, sprite.color.r, sprite.color.g, sprite.color.b ); - - state.setBlending( sprite.blending, sprite.blendEquation, sprite.blendSrc, sprite.blendDst ); - - textures.setTexture2D( sprite.texture, 1 ); - - gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - } - - } - - } - - } - - // restore gl - - state.enable( gl.CULL_FACE ); - state.enable( gl.DEPTH_TEST ); - state.buffers.depth.setMask( true ); - - state.reset(); - - }; - - function createProgram( shader ) { - - var program = gl.createProgram(); - - var fragmentShader = gl.createShader( gl.FRAGMENT_SHADER ); - var vertexShader = gl.createShader( gl.VERTEX_SHADER ); - - var prefix = 'precision ' + capabilities.precision + ' float;\n'; - - gl.shaderSource( fragmentShader, prefix + shader.fragmentShader ); - gl.shaderSource( vertexShader, prefix + shader.vertexShader ); - - gl.compileShader( fragmentShader ); - gl.compileShader( vertexShader ); - - gl.attachShader( program, fragmentShader ); - gl.attachShader( program, vertexShader ); - - gl.linkProgram( program ); - - return program; - - } - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function CanvasTexture( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - - Texture.call( this, canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - this.needsUpdate = true; - -} - -CanvasTexture.prototype = Object.create( Texture.prototype ); -CanvasTexture.prototype.constructor = CanvasTexture; - -/** - * @author mikael emtinger / http://gomo.se/ - * @author alteredq / http://alteredqualia.com/ - */ - -function WebGLSpriteRenderer( renderer, gl, state, textures, capabilities ) { - - var vertexBuffer, elementBuffer; - var program, attributes, uniforms; - - var texture; - - // decompose matrixWorld - - var spritePosition = new Vector3(); - var spriteRotation = new Quaternion(); - var spriteScale = new Vector3(); - - function init() { - - var vertices = new Float32Array( [ - - 0.5, - 0.5, 0, 0, - 0.5, - 0.5, 1, 0, - 0.5, 0.5, 1, 1, - - 0.5, 0.5, 0, 1 - ] ); - - var faces = new Uint16Array( [ - 0, 1, 2, - 0, 2, 3 - ] ); - - vertexBuffer = gl.createBuffer(); - elementBuffer = gl.createBuffer(); - - gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer ); - gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW ); - - gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); - gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, faces, gl.STATIC_DRAW ); - - program = createProgram(); - - attributes = { - position: gl.getAttribLocation( program, 'position' ), - uv: gl.getAttribLocation( program, 'uv' ) - }; - - uniforms = { - uvOffset: gl.getUniformLocation( program, 'uvOffset' ), - uvScale: gl.getUniformLocation( program, 'uvScale' ), - - rotation: gl.getUniformLocation( program, 'rotation' ), - scale: gl.getUniformLocation( program, 'scale' ), - - color: gl.getUniformLocation( program, 'color' ), - map: gl.getUniformLocation( program, 'map' ), - opacity: gl.getUniformLocation( program, 'opacity' ), - - modelViewMatrix: gl.getUniformLocation( program, 'modelViewMatrix' ), - projectionMatrix: gl.getUniformLocation( program, 'projectionMatrix' ), - - fogType: gl.getUniformLocation( program, 'fogType' ), - fogDensity: gl.getUniformLocation( program, 'fogDensity' ), - fogNear: gl.getUniformLocation( program, 'fogNear' ), - fogFar: gl.getUniformLocation( program, 'fogFar' ), - fogColor: gl.getUniformLocation( program, 'fogColor' ), - fogDepth: gl.getUniformLocation( program, 'fogDepth' ), - - alphaTest: gl.getUniformLocation( program, 'alphaTest' ) - }; - - var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); - canvas.width = 8; - canvas.height = 8; - - var context = canvas.getContext( '2d' ); - context.fillStyle = 'white'; - context.fillRect( 0, 0, 8, 8 ); - - texture = new CanvasTexture( canvas ); - - } - - this.render = function ( sprites, scene, camera ) { - - if ( sprites.length === 0 ) return; - - // setup gl - - if ( program === undefined ) { - - init(); - - } - - state.useProgram( program ); - - state.initAttributes(); - state.enableAttribute( attributes.position ); - state.enableAttribute( attributes.uv ); - state.disableUnusedAttributes(); - - state.disable( gl.CULL_FACE ); - state.enable( gl.BLEND ); - - gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer ); - gl.vertexAttribPointer( attributes.position, 2, gl.FLOAT, false, 2 * 8, 0 ); - gl.vertexAttribPointer( attributes.uv, 2, gl.FLOAT, false, 2 * 8, 8 ); - - gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); - - gl.uniformMatrix4fv( uniforms.projectionMatrix, false, camera.projectionMatrix.elements ); - - state.activeTexture( gl.TEXTURE0 ); - gl.uniform1i( uniforms.map, 0 ); - - var oldFogType = 0; - var sceneFogType = 0; - var fog = scene.fog; - - if ( fog ) { - - gl.uniform3f( uniforms.fogColor, fog.color.r, fog.color.g, fog.color.b ); - - if ( fog.isFog ) { - - gl.uniform1f( uniforms.fogNear, fog.near ); - gl.uniform1f( uniforms.fogFar, fog.far ); - - gl.uniform1i( uniforms.fogType, 1 ); - oldFogType = 1; - sceneFogType = 1; - - } else if ( fog.isFogExp2 ) { - - gl.uniform1f( uniforms.fogDensity, fog.density ); - - gl.uniform1i( uniforms.fogType, 2 ); - oldFogType = 2; - sceneFogType = 2; - - } - - } else { - - gl.uniform1i( uniforms.fogType, 0 ); - oldFogType = 0; - sceneFogType = 0; - - } - - - // update positions and sort - - for ( var i = 0, l = sprites.length; i < l; i ++ ) { - - var sprite = sprites[ i ]; - - sprite.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld ); - sprite.z = - sprite.modelViewMatrix.elements[ 14 ]; - - } - - sprites.sort( painterSortStable ); - - // render all sprites - - var scale = []; - - for ( var i = 0, l = sprites.length; i < l; i ++ ) { - - var sprite = sprites[ i ]; - var material = sprite.material; - - if ( material.visible === false ) continue; - - sprite.onBeforeRender( renderer, scene, camera, undefined, material, undefined ); - - gl.uniform1f( uniforms.alphaTest, material.alphaTest ); - gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite.modelViewMatrix.elements ); - - sprite.matrixWorld.decompose( spritePosition, spriteRotation, spriteScale ); - - scale[ 0 ] = spriteScale.x; - scale[ 1 ] = spriteScale.y; - - var fogType = 0; - - if ( scene.fog && material.fog ) { - - fogType = sceneFogType; - - } - - if ( oldFogType !== fogType ) { - - gl.uniform1i( uniforms.fogType, fogType ); - oldFogType = fogType; - - } - - if ( material.map !== null ) { - - gl.uniform2f( uniforms.uvOffset, material.map.offset.x, material.map.offset.y ); - gl.uniform2f( uniforms.uvScale, material.map.repeat.x, material.map.repeat.y ); - - } else { - - gl.uniform2f( uniforms.uvOffset, 0, 0 ); - gl.uniform2f( uniforms.uvScale, 1, 1 ); - - } - - gl.uniform1f( uniforms.opacity, material.opacity ); - gl.uniform3f( uniforms.color, material.color.r, material.color.g, material.color.b ); - - gl.uniform1f( uniforms.rotation, material.rotation ); - gl.uniform2fv( uniforms.scale, scale ); - - state.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha ); - state.buffers.depth.setTest( material.depthTest ); - state.buffers.depth.setMask( material.depthWrite ); - state.buffers.color.setMask( material.colorWrite ); - - textures.setTexture2D( material.map || texture, 0 ); - - gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); - - sprite.onAfterRender( renderer, scene, camera, undefined, material, undefined ); - - } - - // restore gl - - state.enable( gl.CULL_FACE ); - - state.reset(); - - }; - - function createProgram() { - - var program = gl.createProgram(); - - var vertexShader = gl.createShader( gl.VERTEX_SHADER ); - var fragmentShader = gl.createShader( gl.FRAGMENT_SHADER ); - - gl.shaderSource( vertexShader, [ - - 'precision ' + capabilities.precision + ' float;', - - '#define SHADER_NAME ' + 'SpriteMaterial', - - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform float rotation;', - 'uniform vec2 scale;', - 'uniform vec2 uvOffset;', - 'uniform vec2 uvScale;', - - 'attribute vec2 position;', - 'attribute vec2 uv;', - - 'varying vec2 vUV;', - 'varying float fogDepth;', - - 'void main() {', - - ' vUV = uvOffset + uv * uvScale;', - - ' vec2 alignedPosition = position * scale;', - - ' vec2 rotatedPosition;', - ' rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;', - ' rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;', - - ' vec4 mvPosition;', - - ' mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );', - ' mvPosition.xy += rotatedPosition;', - - ' gl_Position = projectionMatrix * mvPosition;', - - ' fogDepth = - mvPosition.z;', - - '}' - - ].join( '\n' ) ); - - gl.shaderSource( fragmentShader, [ - - 'precision ' + capabilities.precision + ' float;', - - '#define SHADER_NAME ' + 'SpriteMaterial', - - 'uniform vec3 color;', - 'uniform sampler2D map;', - 'uniform float opacity;', - - 'uniform int fogType;', - 'uniform vec3 fogColor;', - 'uniform float fogDensity;', - 'uniform float fogNear;', - 'uniform float fogFar;', - 'uniform float alphaTest;', - - 'varying vec2 vUV;', - 'varying float fogDepth;', - - 'void main() {', - - ' vec4 texture = texture2D( map, vUV );', - - ' gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );', - - ' if ( gl_FragColor.a < alphaTest ) discard;', - - ' if ( fogType > 0 ) {', - - ' float fogFactor = 0.0;', - - ' if ( fogType == 1 ) {', - - ' fogFactor = smoothstep( fogNear, fogFar, fogDepth );', - - ' } else {', - - ' const float LOG2 = 1.442695;', - ' fogFactor = exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 );', - ' fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );', - - ' }', - - ' gl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );', - - ' }', - - '}' - - ].join( '\n' ) ); - - gl.compileShader( vertexShader ); - gl.compileShader( fragmentShader ); - - gl.attachShader( program, vertexShader ); - gl.attachShader( program, fragmentShader ); - - gl.linkProgram( program ); - - return program; - - } - - function painterSortStable( a, b ) { - - if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.z !== b.z ) { - - return b.z - a.z; - - } else { - - return b.id - a.id; - - } - - } - -} - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ - -var materialId = 0; - -function Material() { - - Object.defineProperty( this, 'id', { value: materialId ++ } ); - - this.uuid = _Math.generateUUID(); - - this.name = ''; - this.type = 'Material'; - - this.fog = true; - this.lights = true; - - this.blending = NormalBlending; - this.side = FrontSide; - this.flatShading = false; - this.vertexColors = NoColors; // THREE.NoColors, THREE.VertexColors, THREE.FaceColors - - this.opacity = 1; - this.transparent = false; - - this.blendSrc = SrcAlphaFactor; - this.blendDst = OneMinusSrcAlphaFactor; - this.blendEquation = AddEquation; - this.blendSrcAlpha = null; - this.blendDstAlpha = null; - this.blendEquationAlpha = null; - - this.depthFunc = LessEqualDepth; - this.depthTest = true; - this.depthWrite = true; - - this.clippingPlanes = null; - this.clipIntersection = false; - this.clipShadows = false; - - this.colorWrite = true; - - this.precision = null; // override the renderer's default precision for this material - - this.polygonOffset = false; - this.polygonOffsetFactor = 0; - this.polygonOffsetUnits = 0; - - this.dithering = false; - - this.alphaTest = 0; - this.premultipliedAlpha = false; - - this.overdraw = 0; // Overdrawn pixels (typically between 0 and 1) for fixing antialiasing gaps in CanvasRenderer - - this.visible = true; - - this.userData = {}; - - this.needsUpdate = true; - -} - -Object.assign( Material.prototype, EventDispatcher.prototype, { - - isMaterial: true, - - onBeforeCompile: function () {}, - - setValues: function ( values ) { - - if ( values === undefined ) return; - - for ( var key in values ) { - - var newValue = values[ key ]; - - if ( newValue === undefined ) { - - console.warn( "THREE.Material: '" + key + "' parameter is undefined." ); - continue; - - } - - // for backward compatability if shading is set in the constructor - if ( key === 'shading' ) { - - console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' ); - this.flatShading = ( newValue === FlatShading ) ? true : false; - continue; - - } - - var currentValue = this[ key ]; - - if ( currentValue === undefined ) { - - console.warn( "THREE." + this.type + ": '" + key + "' is not a property of this material." ); - continue; - - } - - if ( currentValue && currentValue.isColor ) { - - currentValue.set( newValue ); - - } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) { - - currentValue.copy( newValue ); - - } else if ( key === 'overdraw' ) { - - // ensure overdraw is backwards-compatible with legacy boolean type - this[ key ] = Number( newValue ); - - } else { - - this[ key ] = newValue; - - } - - } - - }, - - toJSON: function ( meta ) { - - var isRoot = ( meta === undefined || typeof meta === 'string' ); - - if ( isRoot ) { - - meta = { - textures: {}, - images: {} - }; - - } - - var data = { - metadata: { - version: 4.5, - type: 'Material', - generator: 'Material.toJSON' - } - }; - - // standard Material serialization - data.uuid = this.uuid; - data.type = this.type; - - if ( this.name !== '' ) data.name = this.name; - - if ( this.color && this.color.isColor ) data.color = this.color.getHex(); - - if ( this.roughness !== undefined ) data.roughness = this.roughness; - if ( this.metalness !== undefined ) data.metalness = this.metalness; - - if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); - if ( this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; - - if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); - if ( this.shininess !== undefined ) data.shininess = this.shininess; - if ( this.clearCoat !== undefined ) data.clearCoat = this.clearCoat; - if ( this.clearCoatRoughness !== undefined ) data.clearCoatRoughness = this.clearCoatRoughness; - - if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; - if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; - if ( this.lightMap && this.lightMap.isTexture ) data.lightMap = this.lightMap.toJSON( meta ).uuid; - if ( this.bumpMap && this.bumpMap.isTexture ) { - - data.bumpMap = this.bumpMap.toJSON( meta ).uuid; - data.bumpScale = this.bumpScale; - - } - if ( this.normalMap && this.normalMap.isTexture ) { - - data.normalMap = this.normalMap.toJSON( meta ).uuid; - data.normalScale = this.normalScale.toArray(); - - } - if ( this.displacementMap && this.displacementMap.isTexture ) { - - data.displacementMap = this.displacementMap.toJSON( meta ).uuid; - data.displacementScale = this.displacementScale; - data.displacementBias = this.displacementBias; - - } - if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; - if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; - - if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; - if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; - - if ( this.envMap && this.envMap.isTexture ) { - - data.envMap = this.envMap.toJSON( meta ).uuid; - data.reflectivity = this.reflectivity; // Scale behind envMap - - } - - if ( this.gradientMap && this.gradientMap.isTexture ) { - - data.gradientMap = this.gradientMap.toJSON( meta ).uuid; - - } - - if ( this.size !== undefined ) data.size = this.size; - if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; - - if ( this.blending !== NormalBlending ) data.blending = this.blending; - if ( this.flatShading === true ) data.flatShading = this.flatShading; - if ( this.side !== FrontSide ) data.side = this.side; - if ( this.vertexColors !== NoColors ) data.vertexColors = this.vertexColors; - - if ( this.opacity < 1 ) data.opacity = this.opacity; - if ( this.transparent === true ) data.transparent = this.transparent; - - data.depthFunc = this.depthFunc; - data.depthTest = this.depthTest; - data.depthWrite = this.depthWrite; - - // rotation (SpriteMaterial) - if ( this.rotation !== 0 ) data.rotation = this.rotation; - - if ( this.linewidth !== 1 ) data.linewidth = this.linewidth; - if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; - if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; - if ( this.scale !== undefined ) data.scale = this.scale; - - if ( this.dithering === true ) data.dithering = true; - - if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; - if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha; - - if ( this.wireframe === true ) data.wireframe = this.wireframe; - if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; - if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; - if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; - - if ( this.morphTargets === true ) data.morphTargets = true; - if ( this.skinning === true ) data.skinning = true; - - if ( this.visible === false ) data.visible = false; - if ( JSON.stringify( this.userData ) !== '{}' ) data.userData = this.userData; - - // TODO: Copied from Object3D.toJSON - - function extractFromCache( cache ) { - - var values = []; - - for ( var key in cache ) { - - var data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - - return values; - - } - - if ( isRoot ) { - - var textures = extractFromCache( meta.textures ); - var images = extractFromCache( meta.images ); - - if ( textures.length > 0 ) data.textures = textures; - if ( images.length > 0 ) data.images = images; - - } - - return data; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( source ) { - - this.name = source.name; - - this.fog = source.fog; - this.lights = source.lights; - - this.blending = source.blending; - this.side = source.side; - this.flatShading = source.flatShading; - this.vertexColors = source.vertexColors; - - this.opacity = source.opacity; - this.transparent = source.transparent; - - this.blendSrc = source.blendSrc; - this.blendDst = source.blendDst; - this.blendEquation = source.blendEquation; - this.blendSrcAlpha = source.blendSrcAlpha; - this.blendDstAlpha = source.blendDstAlpha; - this.blendEquationAlpha = source.blendEquationAlpha; - - this.depthFunc = source.depthFunc; - this.depthTest = source.depthTest; - this.depthWrite = source.depthWrite; - - this.colorWrite = source.colorWrite; - - this.precision = source.precision; - - this.polygonOffset = source.polygonOffset; - this.polygonOffsetFactor = source.polygonOffsetFactor; - this.polygonOffsetUnits = source.polygonOffsetUnits; - - this.dithering = source.dithering; - - this.alphaTest = source.alphaTest; - this.premultipliedAlpha = source.premultipliedAlpha; - - this.overdraw = source.overdraw; - - this.visible = source.visible; - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - this.clipShadows = source.clipShadows; - this.clipIntersection = source.clipIntersection; - - var srcPlanes = source.clippingPlanes, - dstPlanes = null; - - if ( srcPlanes !== null ) { - - var n = srcPlanes.length; - dstPlanes = new Array( n ); - - for ( var i = 0; i !== n; ++ i ) - dstPlanes[ i ] = srcPlanes[ i ].clone(); - - } - - this.clippingPlanes = dstPlanes; - - return this; - - }, - - dispose: function () { - - this.dispatchEvent( { type: 'dispose' } ); - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * @author bhouston / https://clara.io - * @author WestLangley / http://github.com/WestLangley - * - * parameters = { - * - * opacity: , - * - * map: new THREE.Texture( ), - * - * alphaMap: new THREE.Texture( ), - * - * displacementMap: new THREE.Texture( ), - * displacementScale: , - * displacementBias: , - * - * wireframe: , - * wireframeLinewidth: - * } - */ - -function MeshDepthMaterial( parameters ) { - - Material.call( this ); - - this.type = 'MeshDepthMaterial'; - - this.depthPacking = BasicDepthPacking; - - this.skinning = false; - this.morphTargets = false; - - this.map = null; - - this.alphaMap = null; - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.wireframe = false; - this.wireframeLinewidth = 1; - - this.fog = false; - this.lights = false; - - this.setValues( parameters ); - -} - -MeshDepthMaterial.prototype = Object.create( Material.prototype ); -MeshDepthMaterial.prototype.constructor = MeshDepthMaterial; - -MeshDepthMaterial.prototype.isMeshDepthMaterial = true; - -MeshDepthMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.depthPacking = source.depthPacking; - - this.skinning = source.skinning; - this.morphTargets = source.morphTargets; - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - return this; - -}; - -/** - * @author WestLangley / http://github.com/WestLangley - * - * parameters = { - * - * referencePosition: , - * nearDistance: , - * farDistance: , - * - * skinning: , - * morphTargets: , - * - * map: new THREE.Texture( ), - * - * alphaMap: new THREE.Texture( ), - * - * displacementMap: new THREE.Texture( ), - * displacementScale: , - * displacementBias: - * - * } - */ - -function MeshDistanceMaterial( parameters ) { - - Material.call( this ); - - this.type = 'MeshDistanceMaterial'; - - this.referencePosition = new Vector3(); - this.nearDistance = 1; - this.farDistance = 1000; - - this.skinning = false; - this.morphTargets = false; - - this.map = null; - - this.alphaMap = null; - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.fog = false; - this.lights = false; - - this.setValues( parameters ); - -} - -MeshDistanceMaterial.prototype = Object.create( Material.prototype ); -MeshDistanceMaterial.prototype.constructor = MeshDistanceMaterial; - -MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true; - -MeshDistanceMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.referencePosition.copy( source.referencePosition ); - this.nearDistance = source.nearDistance; - this.farDistance = source.farDistance; - - this.skinning = source.skinning; - this.morphTargets = source.morphTargets; - - this.map = source.map; - - this.alphaMap = source.alphaMap; - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - return this; - -}; - -/** - * @author bhouston / http://clara.io - * @author WestLangley / http://github.com/WestLangley - */ - -function Box3( min, max ) { - - this.min = ( min !== undefined ) ? min : new Vector3( + Infinity, + Infinity, + Infinity ); - this.max = ( max !== undefined ) ? max : new Vector3( - Infinity, - Infinity, - Infinity ); - -} - -Object.assign( Box3.prototype, { - - isBox3: true, - - set: function ( min, max ) { - - this.min.copy( min ); - this.max.copy( max ); - - return this; - - }, - - setFromArray: function ( array ) { - - var minX = + Infinity; - var minY = + Infinity; - var minZ = + Infinity; - - var maxX = - Infinity; - var maxY = - Infinity; - var maxZ = - Infinity; - - for ( var i = 0, l = array.length; i < l; i += 3 ) { - - var x = array[ i ]; - var y = array[ i + 1 ]; - var z = array[ i + 2 ]; - - if ( x < minX ) minX = x; - if ( y < minY ) minY = y; - if ( z < minZ ) minZ = z; - - if ( x > maxX ) maxX = x; - if ( y > maxY ) maxY = y; - if ( z > maxZ ) maxZ = z; - - } - - this.min.set( minX, minY, minZ ); - this.max.set( maxX, maxY, maxZ ); - - return this; - - }, - - setFromBufferAttribute: function ( attribute ) { - - var minX = + Infinity; - var minY = + Infinity; - var minZ = + Infinity; - - var maxX = - Infinity; - var maxY = - Infinity; - var maxZ = - Infinity; - - for ( var i = 0, l = attribute.count; i < l; i ++ ) { - - var x = attribute.getX( i ); - var y = attribute.getY( i ); - var z = attribute.getZ( i ); - - if ( x < minX ) minX = x; - if ( y < minY ) minY = y; - if ( z < minZ ) minZ = z; - - if ( x > maxX ) maxX = x; - if ( y > maxY ) maxY = y; - if ( z > maxZ ) maxZ = z; - - } - - this.min.set( minX, minY, minZ ); - this.max.set( maxX, maxY, maxZ ); - - return this; - - }, - - setFromPoints: function ( points ) { - - this.makeEmpty(); - - for ( var i = 0, il = points.length; i < il; i ++ ) { - - this.expandByPoint( points[ i ] ); - - } - - return this; - - }, - - setFromCenterAndSize: function () { - - var v1 = new Vector3(); - - return function setFromCenterAndSize( center, size ) { - - var halfSize = v1.copy( size ).multiplyScalar( 0.5 ); - - this.min.copy( center ).sub( halfSize ); - this.max.copy( center ).add( halfSize ); - - return this; - - }; - - }(), - - setFromObject: function ( object ) { - - this.makeEmpty(); - - return this.expandByObject( object ); - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( box ) { - - this.min.copy( box.min ); - this.max.copy( box.max ); - - return this; - - }, - - makeEmpty: function () { - - this.min.x = this.min.y = this.min.z = + Infinity; - this.max.x = this.max.y = this.max.z = - Infinity; - - return this; - - }, - - isEmpty: function () { - - // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes - - return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); - - }, - - getCenter: function ( optionalTarget ) { - - var result = optionalTarget || new Vector3(); - return this.isEmpty() ? result.set( 0, 0, 0 ) : result.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); - - }, - - getSize: function ( optionalTarget ) { - - var result = optionalTarget || new Vector3(); - return this.isEmpty() ? result.set( 0, 0, 0 ) : result.subVectors( this.max, this.min ); - - }, - - expandByPoint: function ( point ) { - - this.min.min( point ); - this.max.max( point ); - - return this; - - }, - - expandByVector: function ( vector ) { - - this.min.sub( vector ); - this.max.add( vector ); - - return this; - - }, - - expandByScalar: function ( scalar ) { - - this.min.addScalar( - scalar ); - this.max.addScalar( scalar ); - - return this; - - }, - - expandByObject: function () { - - // Computes the world-axis-aligned bounding box of an object (including its children), - // accounting for both the object's, and children's, world transforms - - var scope, i, l; - - var v1 = new Vector3(); - - function traverse( node ) { - - var geometry = node.geometry; - - if ( geometry !== undefined ) { - - if ( geometry.isGeometry ) { - - var vertices = geometry.vertices; - - for ( i = 0, l = vertices.length; i < l; i ++ ) { - - v1.copy( vertices[ i ] ); - v1.applyMatrix4( node.matrixWorld ); - - scope.expandByPoint( v1 ); - - } - - } else if ( geometry.isBufferGeometry ) { - - var attribute = geometry.attributes.position; - - if ( attribute !== undefined ) { - - for ( i = 0, l = attribute.count; i < l; i ++ ) { - - v1.fromBufferAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); - - scope.expandByPoint( v1 ); - - } - - } - - } - - } - - } - - return function expandByObject( object ) { - - scope = this; - - object.updateMatrixWorld( true ); - - object.traverse( traverse ); - - return this; - - }; - - }(), - - containsPoint: function ( point ) { - - return point.x < this.min.x || point.x > this.max.x || - point.y < this.min.y || point.y > this.max.y || - point.z < this.min.z || point.z > this.max.z ? false : true; - - }, - - containsBox: function ( box ) { - - return this.min.x <= box.min.x && box.max.x <= this.max.x && - this.min.y <= box.min.y && box.max.y <= this.max.y && - this.min.z <= box.min.z && box.max.z <= this.max.z; - - }, - - getParameter: function ( point, optionalTarget ) { - - // This can potentially have a divide by zero if the box - // has a size dimension of 0. - - var result = optionalTarget || new Vector3(); - - return result.set( - ( point.x - this.min.x ) / ( this.max.x - this.min.x ), - ( point.y - this.min.y ) / ( this.max.y - this.min.y ), - ( point.z - this.min.z ) / ( this.max.z - this.min.z ) - ); - - }, - - intersectsBox: function ( box ) { - - // using 6 splitting planes to rule out intersections. - return box.max.x < this.min.x || box.min.x > this.max.x || - box.max.y < this.min.y || box.min.y > this.max.y || - box.max.z < this.min.z || box.min.z > this.max.z ? false : true; - - }, - - intersectsSphere: ( function () { - - var closestPoint = new Vector3(); - - return function intersectsSphere( sphere ) { - - // Find the point on the AABB closest to the sphere center. - this.clampPoint( sphere.center, closestPoint ); - - // If that point is inside the sphere, the AABB and sphere intersect. - return closestPoint.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); - - }; - - } )(), - - intersectsPlane: function ( plane ) { - - // We compute the minimum and maximum dot product values. If those values - // are on the same side (back or front) of the plane, then there is no intersection. - - var min, max; - - if ( plane.normal.x > 0 ) { - - min = plane.normal.x * this.min.x; - max = plane.normal.x * this.max.x; - - } else { - - min = plane.normal.x * this.max.x; - max = plane.normal.x * this.min.x; - - } - - if ( plane.normal.y > 0 ) { - - min += plane.normal.y * this.min.y; - max += plane.normal.y * this.max.y; - - } else { - - min += plane.normal.y * this.max.y; - max += plane.normal.y * this.min.y; - - } - - if ( plane.normal.z > 0 ) { - - min += plane.normal.z * this.min.z; - max += plane.normal.z * this.max.z; - - } else { - - min += plane.normal.z * this.max.z; - max += plane.normal.z * this.min.z; - - } - - return ( min <= plane.constant && max >= plane.constant ); - - }, - - clampPoint: function ( point, optionalTarget ) { - - var result = optionalTarget || new Vector3(); - return result.copy( point ).clamp( this.min, this.max ); - - }, - - distanceToPoint: function () { - - var v1 = new Vector3(); - - return function distanceToPoint( point ) { - - var clampedPoint = v1.copy( point ).clamp( this.min, this.max ); - return clampedPoint.sub( point ).length(); - - }; - - }(), - - getBoundingSphere: function () { - - var v1 = new Vector3(); - - return function getBoundingSphere( optionalTarget ) { - - var result = optionalTarget || new Sphere(); - - this.getCenter( result.center ); - - result.radius = this.getSize( v1 ).length() * 0.5; - - return result; - - }; - - }(), - - intersect: function ( box ) { - - this.min.max( box.min ); - this.max.min( box.max ); - - // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values. - if ( this.isEmpty() ) this.makeEmpty(); - - return this; - - }, - - union: function ( box ) { - - this.min.min( box.min ); - this.max.max( box.max ); - - return this; - - }, - - applyMatrix4: function () { - - var points = [ - new Vector3(), - new Vector3(), - new Vector3(), - new Vector3(), - new Vector3(), - new Vector3(), - new Vector3(), - new Vector3() - ]; - - return function applyMatrix4( matrix ) { - - // transform of empty box is an empty box. - if ( this.isEmpty() ) return this; - - // NOTE: I am using a binary pattern to specify all 2^3 combinations below - points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 - points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 - points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 - points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 - points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 - points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 - points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 - points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 - - this.setFromPoints( points ); - - return this; - - }; - - }(), - - translate: function ( offset ) { - - this.min.add( offset ); - this.max.add( offset ); - - return this; - - }, - - equals: function ( box ) { - - return box.min.equals( this.min ) && box.max.equals( this.max ); - - } - -} ); - -/** - * @author bhouston / http://clara.io - * @author mrdoob / http://mrdoob.com/ - */ - -function Sphere( center, radius ) { - - this.center = ( center !== undefined ) ? center : new Vector3(); - this.radius = ( radius !== undefined ) ? radius : 0; - -} - -Object.assign( Sphere.prototype, { - - set: function ( center, radius ) { - - this.center.copy( center ); - this.radius = radius; - - return this; - - }, - - setFromPoints: function () { - - var box = new Box3(); - - return function setFromPoints( points, optionalCenter ) { - - var center = this.center; - - if ( optionalCenter !== undefined ) { - - center.copy( optionalCenter ); - - } else { - - box.setFromPoints( points ).getCenter( center ); - - } - - var maxRadiusSq = 0; - - for ( var i = 0, il = points.length; i < il; i ++ ) { - - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); - - } - - this.radius = Math.sqrt( maxRadiusSq ); - - return this; - - }; - - }(), - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( sphere ) { - - this.center.copy( sphere.center ); - this.radius = sphere.radius; - - return this; - - }, - - empty: function () { - - return ( this.radius <= 0 ); - - }, - - containsPoint: function ( point ) { - - return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); - - }, - - distanceToPoint: function ( point ) { - - return ( point.distanceTo( this.center ) - this.radius ); - - }, - - intersectsSphere: function ( sphere ) { - - var radiusSum = this.radius + sphere.radius; - - return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); - - }, - - intersectsBox: function ( box ) { - - return box.intersectsSphere( this ); - - }, - - intersectsPlane: function ( plane ) { - - return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; - - }, - - clampPoint: function ( point, optionalTarget ) { - - var deltaLengthSq = this.center.distanceToSquared( point ); - - var result = optionalTarget || new Vector3(); - - result.copy( point ); - - if ( deltaLengthSq > ( this.radius * this.radius ) ) { - - result.sub( this.center ).normalize(); - result.multiplyScalar( this.radius ).add( this.center ); - - } - - return result; - - }, - - getBoundingBox: function ( optionalTarget ) { - - var box = optionalTarget || new Box3(); - - box.set( this.center, this.center ); - box.expandByScalar( this.radius ); - - return box; - - }, - - applyMatrix4: function ( matrix ) { - - this.center.applyMatrix4( matrix ); - this.radius = this.radius * matrix.getMaxScaleOnAxis(); - - return this; - - }, - - translate: function ( offset ) { - - this.center.add( offset ); - - return this; - - }, - - equals: function ( sphere ) { - - return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); - - } - -} ); - -/** - * @author bhouston / http://clara.io - */ - -function Plane( normal, constant ) { - - // normal is assumed to be normalized - - this.normal = ( normal !== undefined ) ? normal : new Vector3( 1, 0, 0 ); - this.constant = ( constant !== undefined ) ? constant : 0; - -} - -Object.assign( Plane.prototype, { - - set: function ( normal, constant ) { - - this.normal.copy( normal ); - this.constant = constant; - - return this; - - }, - - setComponents: function ( x, y, z, w ) { - - this.normal.set( x, y, z ); - this.constant = w; - - return this; - - }, - - setFromNormalAndCoplanarPoint: function ( normal, point ) { - - this.normal.copy( normal ); - this.constant = - point.dot( this.normal ); - - return this; - - }, - - setFromCoplanarPoints: function () { - - var v1 = new Vector3(); - var v2 = new Vector3(); - - return function setFromCoplanarPoints( a, b, c ) { - - var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize(); - - // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? - - this.setFromNormalAndCoplanarPoint( normal, a ); - - return this; - - }; - - }(), - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( plane ) { - - this.normal.copy( plane.normal ); - this.constant = plane.constant; - - return this; - - }, - - normalize: function () { - - // Note: will lead to a divide by zero if the plane is invalid. - - var inverseNormalLength = 1.0 / this.normal.length(); - this.normal.multiplyScalar( inverseNormalLength ); - this.constant *= inverseNormalLength; - - return this; - - }, - - negate: function () { - - this.constant *= - 1; - this.normal.negate(); - - return this; - - }, - - distanceToPoint: function ( point ) { - - return this.normal.dot( point ) + this.constant; - - }, - - distanceToSphere: function ( sphere ) { - - return this.distanceToPoint( sphere.center ) - sphere.radius; - - }, - - projectPoint: function ( point, optionalTarget ) { - - var result = optionalTarget || new Vector3(); - - return result.copy( this.normal ).multiplyScalar( - this.distanceToPoint( point ) ).add( point ); - - }, - - intersectLine: function () { - - var v1 = new Vector3(); - - return function intersectLine( line, optionalTarget ) { - - var result = optionalTarget || new Vector3(); - - var direction = line.delta( v1 ); - - var denominator = this.normal.dot( direction ); - - if ( denominator === 0 ) { - - // line is coplanar, return origin - if ( this.distanceToPoint( line.start ) === 0 ) { - - return result.copy( line.start ); - - } - - // Unsure if this is the correct method to handle this case. - return undefined; - - } - - var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; - - if ( t < 0 || t > 1 ) { - - return undefined; - - } - - return result.copy( direction ).multiplyScalar( t ).add( line.start ); - - }; - - }(), - - intersectsLine: function ( line ) { - - // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. - - var startSign = this.distanceToPoint( line.start ); - var endSign = this.distanceToPoint( line.end ); - - return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); - - }, - - intersectsBox: function ( box ) { - - return box.intersectsPlane( this ); - - }, - - intersectsSphere: function ( sphere ) { - - return sphere.intersectsPlane( this ); - - }, - - coplanarPoint: function ( optionalTarget ) { - - var result = optionalTarget || new Vector3(); - - return result.copy( this.normal ).multiplyScalar( - this.constant ); - - }, - - applyMatrix4: function () { - - var v1 = new Vector3(); - var m1 = new Matrix3(); - - return function applyMatrix4( matrix, optionalNormalMatrix ) { - - var normalMatrix = optionalNormalMatrix || m1.getNormalMatrix( matrix ); - - var referencePoint = this.coplanarPoint( v1 ).applyMatrix4( matrix ); - - var normal = this.normal.applyMatrix3( normalMatrix ).normalize(); - - this.constant = - referencePoint.dot( normal ); - - return this; - - }; - - }(), - - translate: function ( offset ) { - - this.constant -= offset.dot( this.normal ); - - return this; - - }, - - equals: function ( plane ) { - - return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * @author bhouston / http://clara.io - */ - -function Frustum( p0, p1, p2, p3, p4, p5 ) { - - this.planes = [ - - ( p0 !== undefined ) ? p0 : new Plane(), - ( p1 !== undefined ) ? p1 : new Plane(), - ( p2 !== undefined ) ? p2 : new Plane(), - ( p3 !== undefined ) ? p3 : new Plane(), - ( p4 !== undefined ) ? p4 : new Plane(), - ( p5 !== undefined ) ? p5 : new Plane() - - ]; - -} - -Object.assign( Frustum.prototype, { - - set: function ( p0, p1, p2, p3, p4, p5 ) { - - var planes = this.planes; - - planes[ 0 ].copy( p0 ); - planes[ 1 ].copy( p1 ); - planes[ 2 ].copy( p2 ); - planes[ 3 ].copy( p3 ); - planes[ 4 ].copy( p4 ); - planes[ 5 ].copy( p5 ); - - return this; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( frustum ) { - - var planes = this.planes; - - for ( var i = 0; i < 6; i ++ ) { - - planes[ i ].copy( frustum.planes[ i ] ); - - } - - return this; - - }, - - setFromMatrix: function ( m ) { - - var planes = this.planes; - var me = m.elements; - var me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; - var me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; - var me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; - var me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; - - planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); - planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); - planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); - planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); - planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); - planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); - - return this; - - }, - - intersectsObject: function () { - - var sphere = new Sphere(); - - return function intersectsObject( object ) { - - var geometry = object.geometry; - - if ( geometry.boundingSphere === null ) - geometry.computeBoundingSphere(); - - sphere.copy( geometry.boundingSphere ) - .applyMatrix4( object.matrixWorld ); - - return this.intersectsSphere( sphere ); - - }; - - }(), - - intersectsSprite: function () { - - var sphere = new Sphere(); - - return function intersectsSprite( sprite ) { - - sphere.center.set( 0, 0, 0 ); - sphere.radius = 0.7071067811865476; - sphere.applyMatrix4( sprite.matrixWorld ); - - return this.intersectsSphere( sphere ); - - }; - - }(), - - intersectsSphere: function ( sphere ) { - - var planes = this.planes; - var center = sphere.center; - var negRadius = - sphere.radius; - - for ( var i = 0; i < 6; i ++ ) { - - var distance = planes[ i ].distanceToPoint( center ); - - if ( distance < negRadius ) { - - return false; - - } - - } - - return true; - - }, - - intersectsBox: function () { - - var p1 = new Vector3(), - p2 = new Vector3(); - - return function intersectsBox( box ) { - - var planes = this.planes; - - for ( var i = 0; i < 6; i ++ ) { - - var plane = planes[ i ]; - - p1.x = plane.normal.x > 0 ? box.min.x : box.max.x; - p2.x = plane.normal.x > 0 ? box.max.x : box.min.x; - p1.y = plane.normal.y > 0 ? box.min.y : box.max.y; - p2.y = plane.normal.y > 0 ? box.max.y : box.min.y; - p1.z = plane.normal.z > 0 ? box.min.z : box.max.z; - p2.z = plane.normal.z > 0 ? box.max.z : box.min.z; - - var d1 = plane.distanceToPoint( p1 ); - var d2 = plane.distanceToPoint( p2 ); - - // if both outside plane, no intersection - - if ( d1 < 0 && d2 < 0 ) { - - return false; - - } - - } - - return true; - - }; - - }(), - - containsPoint: function ( point ) { - - var planes = this.planes; - - for ( var i = 0; i < 6; i ++ ) { - - if ( planes[ i ].distanceToPoint( point ) < 0 ) { - - return false; - - } - - } - - return true; - - } - -} ); - -/** - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLShadowMap( _renderer, _objects, maxTextureSize ) { - - var _frustum = new Frustum(), - _projScreenMatrix = new Matrix4(), - - _shadowMapSize = new Vector2(), - _maxShadowMapSize = new Vector2( maxTextureSize, maxTextureSize ), - - _lookTarget = new Vector3(), - _lightPositionWorld = new Vector3(), - - _MorphingFlag = 1, - _SkinningFlag = 2, - - _NumberOfMaterialVariants = ( _MorphingFlag | _SkinningFlag ) + 1, - - _depthMaterials = new Array( _NumberOfMaterialVariants ), - _distanceMaterials = new Array( _NumberOfMaterialVariants ), - - _materialCache = {}; - - var cubeDirections = [ - new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ), - new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 ) - ]; - - var cubeUps = [ - new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), - new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 ) - ]; - - var cube2DViewPorts = [ - new Vector4(), new Vector4(), new Vector4(), - new Vector4(), new Vector4(), new Vector4() - ]; - - // init - - for ( var i = 0; i !== _NumberOfMaterialVariants; ++ i ) { - - var useMorphing = ( i & _MorphingFlag ) !== 0; - var useSkinning = ( i & _SkinningFlag ) !== 0; - - var depthMaterial = new MeshDepthMaterial( { - - depthPacking: RGBADepthPacking, - - morphTargets: useMorphing, - skinning: useSkinning - - } ); - - _depthMaterials[ i ] = depthMaterial; - - // - - var distanceMaterial = new MeshDistanceMaterial( { - - morphTargets: useMorphing, - skinning: useSkinning - - } ); - - _distanceMaterials[ i ] = distanceMaterial; - - } - - // - - var scope = this; - - this.enabled = false; - - this.autoUpdate = true; - this.needsUpdate = false; - - this.type = PCFShadowMap; - - this.renderReverseSided = true; - this.renderSingleSided = true; - - this.render = function ( lights, scene, camera ) { - - if ( scope.enabled === false ) return; - if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; - - if ( lights.length === 0 ) return; - - // TODO Clean up (needed in case of contextlost) - var _gl = _renderer.context; - var _state = _renderer.state; - - // Set GL state for depth map. - _state.disable( _gl.BLEND ); - _state.buffers.color.setClear( 1, 1, 1, 1 ); - _state.buffers.depth.setTest( true ); - _state.setScissorTest( false ); - - // render depth map - - var faceCount; - - for ( var i = 0, il = lights.length; i < il; i ++ ) { - - var light = lights[ i ]; - var shadow = light.shadow; - var isPointLight = light && light.isPointLight; - - if ( shadow === undefined ) { - - console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' ); - continue; - - } - - var shadowCamera = shadow.camera; - - _shadowMapSize.copy( shadow.mapSize ); - _shadowMapSize.min( _maxShadowMapSize ); - - if ( isPointLight ) { - - var vpWidth = _shadowMapSize.x; - var vpHeight = _shadowMapSize.y; - - // These viewports map a cube-map onto a 2D texture with the - // following orientation: - // - // xzXZ - // y Y - // - // X - Positive x direction - // x - Negative x direction - // Y - Positive y direction - // y - Negative y direction - // Z - Positive z direction - // z - Negative z direction - - // positive X - cube2DViewPorts[ 0 ].set( vpWidth * 2, vpHeight, vpWidth, vpHeight ); - // negative X - cube2DViewPorts[ 1 ].set( 0, vpHeight, vpWidth, vpHeight ); - // positive Z - cube2DViewPorts[ 2 ].set( vpWidth * 3, vpHeight, vpWidth, vpHeight ); - // negative Z - cube2DViewPorts[ 3 ].set( vpWidth, vpHeight, vpWidth, vpHeight ); - // positive Y - cube2DViewPorts[ 4 ].set( vpWidth * 3, 0, vpWidth, vpHeight ); - // negative Y - cube2DViewPorts[ 5 ].set( vpWidth, 0, vpWidth, vpHeight ); - - _shadowMapSize.x *= 4.0; - _shadowMapSize.y *= 2.0; - - } - - if ( shadow.map === null ) { - - var pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat }; - - shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars ); - shadow.map.texture.name = light.name + ".shadowMap"; - - shadowCamera.updateProjectionMatrix(); - - } - - if ( shadow.isSpotLightShadow ) { - - shadow.update( light ); - - } - - var shadowMap = shadow.map; - var shadowMatrix = shadow.matrix; - - _lightPositionWorld.setFromMatrixPosition( light.matrixWorld ); - shadowCamera.position.copy( _lightPositionWorld ); - - if ( isPointLight ) { - - faceCount = 6; - - // for point lights we set the shadow matrix to be a translation-only matrix - // equal to inverse of the light's position - - shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z ); - - } else { - - faceCount = 1; - - _lookTarget.setFromMatrixPosition( light.target.matrixWorld ); - shadowCamera.lookAt( _lookTarget ); - shadowCamera.updateMatrixWorld(); - - // compute shadow matrix - - shadowMatrix.set( - 0.5, 0.0, 0.0, 0.5, - 0.0, 0.5, 0.0, 0.5, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0 - ); - - shadowMatrix.multiply( shadowCamera.projectionMatrix ); - shadowMatrix.multiply( shadowCamera.matrixWorldInverse ); - - } - - _renderer.setRenderTarget( shadowMap ); - _renderer.clear(); - - // render shadow map for each cube face (if omni-directional) or - // run a single pass if not - - for ( var face = 0; face < faceCount; face ++ ) { - - if ( isPointLight ) { - - _lookTarget.copy( shadowCamera.position ); - _lookTarget.add( cubeDirections[ face ] ); - shadowCamera.up.copy( cubeUps[ face ] ); - shadowCamera.lookAt( _lookTarget ); - shadowCamera.updateMatrixWorld(); - - var vpDimensions = cube2DViewPorts[ face ]; - _state.viewport( vpDimensions ); - - } - - // update camera matrices and frustum - - _projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); - _frustum.setFromMatrix( _projScreenMatrix ); - - // set object matrices & frustum culling - - renderObject( scene, camera, shadowCamera, isPointLight ); - - } - - } - - scope.needsUpdate = false; - - }; - - function getDepthMaterial( object, material, isPointLight, lightPositionWorld, shadowCameraNear, shadowCameraFar ) { - - var geometry = object.geometry; - - var result = null; - - var materialVariants = _depthMaterials; - var customMaterial = object.customDepthMaterial; - - if ( isPointLight ) { - - materialVariants = _distanceMaterials; - customMaterial = object.customDistanceMaterial; - - } - - if ( ! customMaterial ) { - - var useMorphing = false; - - if ( material.morphTargets ) { - - if ( geometry && geometry.isBufferGeometry ) { - - useMorphing = geometry.morphAttributes && geometry.morphAttributes.position && geometry.morphAttributes.position.length > 0; - - } else if ( geometry && geometry.isGeometry ) { - - useMorphing = geometry.morphTargets && geometry.morphTargets.length > 0; - - } - - } - - if ( object.isSkinnedMesh && material.skinning === false ) { - - console.warn( 'THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:', object ); - - } - - var useSkinning = object.isSkinnedMesh && material.skinning; - - var variantIndex = 0; - - if ( useMorphing ) variantIndex |= _MorphingFlag; - if ( useSkinning ) variantIndex |= _SkinningFlag; - - result = materialVariants[ variantIndex ]; - - } else { - - result = customMaterial; - - } - - if ( _renderer.localClippingEnabled && - material.clipShadows === true && - material.clippingPlanes.length !== 0 ) { - - // in this case we need a unique material instance reflecting the - // appropriate state - - var keyA = result.uuid, keyB = material.uuid; - - var materialsForVariant = _materialCache[ keyA ]; - - if ( materialsForVariant === undefined ) { - - materialsForVariant = {}; - _materialCache[ keyA ] = materialsForVariant; - - } - - var cachedMaterial = materialsForVariant[ keyB ]; - - if ( cachedMaterial === undefined ) { - - cachedMaterial = result.clone(); - materialsForVariant[ keyB ] = cachedMaterial; - - } - - result = cachedMaterial; - - } - - result.visible = material.visible; - result.wireframe = material.wireframe; - - var side = material.side; - - if ( scope.renderSingleSided && side == DoubleSide ) { - - side = FrontSide; - - } - - if ( scope.renderReverseSided ) { - - if ( side === FrontSide ) side = BackSide; - else if ( side === BackSide ) side = FrontSide; - - } - - result.side = side; - - result.clipShadows = material.clipShadows; - result.clippingPlanes = material.clippingPlanes; - result.clipIntersection = material.clipIntersection; - - result.wireframeLinewidth = material.wireframeLinewidth; - result.linewidth = material.linewidth; - - if ( isPointLight && result.isMeshDistanceMaterial ) { - - result.referencePosition.copy( lightPositionWorld ); - result.nearDistance = shadowCameraNear; - result.farDistance = shadowCameraFar; - - } - - return result; - - } - - function renderObject( object, camera, shadowCamera, isPointLight ) { - - if ( object.visible === false ) return; - - var visible = object.layers.test( camera.layers ); - - if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { - - if ( object.castShadow && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) { - - object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); - - var geometry = _objects.update( object ); - var material = object.material; - - if ( Array.isArray( material ) ) { - - var groups = geometry.groups; - - for ( var k = 0, kl = groups.length; k < kl; k ++ ) { - - var group = groups[ k ]; - var groupMaterial = material[ group.materialIndex ]; - - if ( groupMaterial && groupMaterial.visible ) { - - var depthMaterial = getDepthMaterial( object, groupMaterial, isPointLight, _lightPositionWorld, shadowCamera.near, shadowCamera.far ); - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); - - } - - } - - } else if ( material.visible ) { - - var depthMaterial = getDepthMaterial( object, material, isPointLight, _lightPositionWorld, shadowCamera.near, shadowCamera.far ); - _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); - - } - - } - - } - - var children = object.children; - - for ( var i = 0, l = children.length; i < l; i ++ ) { - - renderObject( children[ i ], camera, shadowCamera, isPointLight ); - - } - - } - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLAttributes( gl ) { - - var buffers = {}; - - function createBuffer( attribute, bufferType ) { - - var array = attribute.array; - var usage = attribute.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW; - - var buffer = gl.createBuffer(); - - gl.bindBuffer( bufferType, buffer ); - gl.bufferData( bufferType, array, usage ); - - attribute.onUploadCallback(); - - var type = gl.FLOAT; - - if ( array instanceof Float32Array ) { - - type = gl.FLOAT; - - } else if ( array instanceof Float64Array ) { - - console.warn( 'THREE.WebGLAttributes: Unsupported data buffer format: Float64Array.' ); - - } else if ( array instanceof Uint16Array ) { - - type = gl.UNSIGNED_SHORT; - - } else if ( array instanceof Int16Array ) { - - type = gl.SHORT; - - } else if ( array instanceof Uint32Array ) { - - type = gl.UNSIGNED_INT; - - } else if ( array instanceof Int32Array ) { - - type = gl.INT; - - } else if ( array instanceof Int8Array ) { - - type = gl.BYTE; - - } else if ( array instanceof Uint8Array ) { - - type = gl.UNSIGNED_BYTE; - - } - - return { - buffer: buffer, - type: type, - bytesPerElement: array.BYTES_PER_ELEMENT, - version: attribute.version - }; - - } - - function updateBuffer( buffer, attribute, bufferType ) { - - var array = attribute.array; - var updateRange = attribute.updateRange; - - gl.bindBuffer( bufferType, buffer ); - - if ( attribute.dynamic === false ) { - - gl.bufferData( bufferType, array, gl.STATIC_DRAW ); - - } else if ( updateRange.count === - 1 ) { - - // Not using update ranges - - gl.bufferSubData( bufferType, 0, array ); - - } else if ( updateRange.count === 0 ) { - - console.error( 'THREE.WebGLObjects.updateBuffer: dynamic THREE.BufferAttribute marked as needsUpdate but updateRange.count is 0, ensure you are using set methods or updating manually.' ); - - } else { - - gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, - array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) ); - - updateRange.count = - 1; // reset range - - } - - } - - // - - function get( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - return buffers[ attribute.uuid ]; - - } - - function remove( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - var data = buffers[ attribute.uuid ]; - - if ( data ) { - - gl.deleteBuffer( data.buffer ); - - delete buffers[ attribute.uuid ]; - - } - - } - - function update( attribute, bufferType ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - var data = buffers[ attribute.uuid ]; - - if ( data === undefined ) { - - buffers[ attribute.uuid ] = createBuffer( attribute, bufferType ); - - } else if ( data.version < attribute.version ) { - - updateBuffer( data.buffer, attribute, bufferType ); - - data.version = attribute.version; - - } - - } - - return { - - get: get, - remove: remove, - update: update - - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - * @author WestLangley / http://github.com/WestLangley - * @author bhouston / http://clara.io - */ - -function Euler( x, y, z, order ) { - - this._x = x || 0; - this._y = y || 0; - this._z = z || 0; - this._order = order || Euler.DefaultOrder; - -} - -Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ]; - -Euler.DefaultOrder = 'XYZ'; - -Object.defineProperties( Euler.prototype, { - - x: { - - get: function () { - - return this._x; - - }, - - set: function ( value ) { - - this._x = value; - this.onChangeCallback(); - - } - - }, - - y: { - - get: function () { - - return this._y; - - }, - - set: function ( value ) { - - this._y = value; - this.onChangeCallback(); - - } - - }, - - z: { - - get: function () { - - return this._z; - - }, - - set: function ( value ) { - - this._z = value; - this.onChangeCallback(); - - } - - }, - - order: { - - get: function () { - - return this._order; - - }, - - set: function ( value ) { - - this._order = value; - this.onChangeCallback(); - - } - - } - -} ); - -Object.assign( Euler.prototype, { - - isEuler: true, - - set: function ( x, y, z, order ) { - - this._x = x; - this._y = y; - this._z = z; - this._order = order || this._order; - - this.onChangeCallback(); - - return this; - - }, - - clone: function () { - - return new this.constructor( this._x, this._y, this._z, this._order ); - - }, - - copy: function ( euler ) { - - this._x = euler._x; - this._y = euler._y; - this._z = euler._z; - this._order = euler._order; - - this.onChangeCallback(); - - return this; - - }, - - setFromRotationMatrix: function ( m, order, update ) { - - var clamp = _Math.clamp; - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - var te = m.elements; - var m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; - var m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; - var m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; - - order = order || this._order; - - if ( order === 'XYZ' ) { - - this._y = Math.asin( clamp( m13, - 1, 1 ) ); - - if ( Math.abs( m13 ) < 0.99999 ) { - - this._x = Math.atan2( - m23, m33 ); - this._z = Math.atan2( - m12, m11 ); - - } else { - - this._x = Math.atan2( m32, m22 ); - this._z = 0; - - } - - } else if ( order === 'YXZ' ) { - - this._x = Math.asin( - clamp( m23, - 1, 1 ) ); - - if ( Math.abs( m23 ) < 0.99999 ) { - - this._y = Math.atan2( m13, m33 ); - this._z = Math.atan2( m21, m22 ); - - } else { - - this._y = Math.atan2( - m31, m11 ); - this._z = 0; - - } - - } else if ( order === 'ZXY' ) { - - this._x = Math.asin( clamp( m32, - 1, 1 ) ); - - if ( Math.abs( m32 ) < 0.99999 ) { - - this._y = Math.atan2( - m31, m33 ); - this._z = Math.atan2( - m12, m22 ); - - } else { - - this._y = 0; - this._z = Math.atan2( m21, m11 ); - - } - - } else if ( order === 'ZYX' ) { - - this._y = Math.asin( - clamp( m31, - 1, 1 ) ); - - if ( Math.abs( m31 ) < 0.99999 ) { - - this._x = Math.atan2( m32, m33 ); - this._z = Math.atan2( m21, m11 ); - - } else { - - this._x = 0; - this._z = Math.atan2( - m12, m22 ); - - } - - } else if ( order === 'YZX' ) { - - this._z = Math.asin( clamp( m21, - 1, 1 ) ); - - if ( Math.abs( m21 ) < 0.99999 ) { - - this._x = Math.atan2( - m23, m22 ); - this._y = Math.atan2( - m31, m11 ); - - } else { - - this._x = 0; - this._y = Math.atan2( m13, m33 ); - - } - - } else if ( order === 'XZY' ) { - - this._z = Math.asin( - clamp( m12, - 1, 1 ) ); - - if ( Math.abs( m12 ) < 0.99999 ) { - - this._x = Math.atan2( m32, m22 ); - this._y = Math.atan2( m13, m11 ); - - } else { - - this._x = Math.atan2( - m23, m33 ); - this._y = 0; - - } - - } else { - - console.warn( 'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order ); - - } - - this._order = order; - - if ( update !== false ) this.onChangeCallback(); - - return this; - - }, - - setFromQuaternion: function () { - - var matrix = new Matrix4(); - - return function setFromQuaternion( q, order, update ) { - - matrix.makeRotationFromQuaternion( q ); - - return this.setFromRotationMatrix( matrix, order, update ); - - }; - - }(), - - setFromVector3: function ( v, order ) { - - return this.set( v.x, v.y, v.z, order || this._order ); - - }, - - reorder: function () { - - // WARNING: this discards revolution information -bhouston - - var q = new Quaternion(); - - return function reorder( newOrder ) { - - q.setFromEuler( this ); - - return this.setFromQuaternion( q, newOrder ); - - }; - - }(), - - equals: function ( euler ) { - - return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); - - }, - - fromArray: function ( array ) { - - this._x = array[ 0 ]; - this._y = array[ 1 ]; - this._z = array[ 2 ]; - if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; - - this.onChangeCallback(); - - return this; - - }, - - toArray: function ( array, offset ) { - - if ( array === undefined ) array = []; - if ( offset === undefined ) offset = 0; - - array[ offset ] = this._x; - array[ offset + 1 ] = this._y; - array[ offset + 2 ] = this._z; - array[ offset + 3 ] = this._order; - - return array; - - }, - - toVector3: function ( optionalResult ) { - - if ( optionalResult ) { - - return optionalResult.set( this._x, this._y, this._z ); - - } else { - - return new Vector3( this._x, this._y, this._z ); - - } - - }, - - onChange: function ( callback ) { - - this.onChangeCallback = callback; - - return this; - - }, - - onChangeCallback: function () {} - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function Layers() { - - this.mask = 1 | 0; - -} - -Object.assign( Layers.prototype, { - - set: function ( channel ) { - - this.mask = 1 << channel | 0; - - }, - - enable: function ( channel ) { - - this.mask |= 1 << channel | 0; - - }, - - toggle: function ( channel ) { - - this.mask ^= 1 << channel | 0; - - }, - - disable: function ( channel ) { - - this.mask &= ~ ( 1 << channel | 0 ); - - }, - - test: function ( layers ) { - - return ( this.mask & layers.mask ) !== 0; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author mikael emtinger / http://gomo.se/ - * @author alteredq / http://alteredqualia.com/ - * @author WestLangley / http://github.com/WestLangley - * @author elephantatwork / www.elephantatwork.ch - */ - -var object3DId = 0; - -function Object3D() { - - Object.defineProperty( this, 'id', { value: object3DId ++ } ); - - this.uuid = _Math.generateUUID(); - - this.name = ''; - this.type = 'Object3D'; - - this.parent = null; - this.children = []; - - this.up = Object3D.DefaultUp.clone(); - - var position = new Vector3(); - var rotation = new Euler(); - var quaternion = new Quaternion(); - var scale = new Vector3( 1, 1, 1 ); - - function onRotationChange() { - - quaternion.setFromEuler( rotation, false ); - - } - - function onQuaternionChange() { - - rotation.setFromQuaternion( quaternion, undefined, false ); - - } - - rotation.onChange( onRotationChange ); - quaternion.onChange( onQuaternionChange ); - - Object.defineProperties( this, { - position: { - enumerable: true, - value: position - }, - rotation: { - enumerable: true, - value: rotation - }, - quaternion: { - enumerable: true, - value: quaternion - }, - scale: { - enumerable: true, - value: scale - }, - modelViewMatrix: { - value: new Matrix4() - }, - normalMatrix: { - value: new Matrix3() - } - } ); - - this.matrix = new Matrix4(); - this.matrixWorld = new Matrix4(); - - this.matrixAutoUpdate = Object3D.DefaultMatrixAutoUpdate; - this.matrixWorldNeedsUpdate = false; - - this.layers = new Layers(); - this.visible = true; - - this.castShadow = false; - this.receiveShadow = false; - - this.frustumCulled = true; - this.renderOrder = 0; - - this.userData = {}; - -} - -Object3D.DefaultUp = new Vector3( 0, 1, 0 ); -Object3D.DefaultMatrixAutoUpdate = true; - -Object.assign( Object3D.prototype, EventDispatcher.prototype, { - - isObject3D: true, - - onBeforeRender: function () {}, - onAfterRender: function () {}, - - applyMatrix: function ( matrix ) { - - this.matrix.multiplyMatrices( matrix, this.matrix ); - - this.matrix.decompose( this.position, this.quaternion, this.scale ); - - }, - - applyQuaternion: function ( q ) { - - this.quaternion.premultiply( q ); - - return this; - - }, - - setRotationFromAxisAngle: function ( axis, angle ) { - - // assumes axis is normalized - - this.quaternion.setFromAxisAngle( axis, angle ); - - }, - - setRotationFromEuler: function ( euler ) { - - this.quaternion.setFromEuler( euler, true ); - - }, - - setRotationFromMatrix: function ( m ) { - - // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) - - this.quaternion.setFromRotationMatrix( m ); - - }, - - setRotationFromQuaternion: function ( q ) { - - // assumes q is normalized - - this.quaternion.copy( q ); - - }, - - rotateOnAxis: function () { - - // rotate object on axis in object space - // axis is assumed to be normalized - - var q1 = new Quaternion(); - - return function rotateOnAxis( axis, angle ) { - - q1.setFromAxisAngle( axis, angle ); - - this.quaternion.multiply( q1 ); - - return this; - - }; - - }(), - - rotateOnWorldAxis: function () { - - // rotate object on axis in world space - // axis is assumed to be normalized - // method assumes no rotated parent - - var q1 = new Quaternion(); - - return function rotateOnWorldAxis( axis, angle ) { - - q1.setFromAxisAngle( axis, angle ); - - this.quaternion.premultiply( q1 ); - - return this; - - }; - - }(), - - rotateX: function () { - - var v1 = new Vector3( 1, 0, 0 ); - - return function rotateX( angle ) { - - return this.rotateOnAxis( v1, angle ); - - }; - - }(), - - rotateY: function () { - - var v1 = new Vector3( 0, 1, 0 ); - - return function rotateY( angle ) { - - return this.rotateOnAxis( v1, angle ); - - }; - - }(), - - rotateZ: function () { - - var v1 = new Vector3( 0, 0, 1 ); - - return function rotateZ( angle ) { - - return this.rotateOnAxis( v1, angle ); - - }; - - }(), - - translateOnAxis: function () { - - // translate object by distance along axis in object space - // axis is assumed to be normalized - - var v1 = new Vector3(); - - return function translateOnAxis( axis, distance ) { - - v1.copy( axis ).applyQuaternion( this.quaternion ); - - this.position.add( v1.multiplyScalar( distance ) ); - - return this; - - }; - - }(), - - translateX: function () { - - var v1 = new Vector3( 1, 0, 0 ); - - return function translateX( distance ) { - - return this.translateOnAxis( v1, distance ); - - }; - - }(), - - translateY: function () { - - var v1 = new Vector3( 0, 1, 0 ); - - return function translateY( distance ) { - - return this.translateOnAxis( v1, distance ); - - }; - - }(), - - translateZ: function () { - - var v1 = new Vector3( 0, 0, 1 ); - - return function translateZ( distance ) { - - return this.translateOnAxis( v1, distance ); - - }; - - }(), - - localToWorld: function ( vector ) { - - return vector.applyMatrix4( this.matrixWorld ); - - }, - - worldToLocal: function () { - - var m1 = new Matrix4(); - - return function worldToLocal( vector ) { - - return vector.applyMatrix4( m1.getInverse( this.matrixWorld ) ); - - }; - - }(), - - lookAt: function () { - - // This method does not support objects with rotated and/or translated parent(s) - - var m1 = new Matrix4(); - var vector = new Vector3(); - - return function lookAt( x, y, z ) { - - if ( x.isVector3 ) { - - vector.copy( x ); - - } else { - - vector.set( x, y, z ); - - } - - if ( this.isCamera ) { - - m1.lookAt( this.position, vector, this.up ); - - } else { - - m1.lookAt( vector, this.position, this.up ); - - } - - this.quaternion.setFromRotationMatrix( m1 ); - - }; - - }(), - - add: function ( object ) { - - if ( arguments.length > 1 ) { - - for ( var i = 0; i < arguments.length; i ++ ) { - - this.add( arguments[ i ] ); - - } - - return this; - - } - - if ( object === this ) { - - console.error( "THREE.Object3D.add: object can't be added as a child of itself.", object ); - return this; - - } - - if ( ( object && object.isObject3D ) ) { - - if ( object.parent !== null ) { - - object.parent.remove( object ); - - } - - object.parent = this; - object.dispatchEvent( { type: 'added' } ); - - this.children.push( object ); - - } else { - - console.error( "THREE.Object3D.add: object not an instance of THREE.Object3D.", object ); - - } - - return this; - - }, - - remove: function ( object ) { - - if ( arguments.length > 1 ) { - - for ( var i = 0; i < arguments.length; i ++ ) { - - this.remove( arguments[ i ] ); - - } - - return this; - - } - - var index = this.children.indexOf( object ); - - if ( index !== - 1 ) { - - object.parent = null; - - object.dispatchEvent( { type: 'removed' } ); - - this.children.splice( index, 1 ); - - } - - return this; - - }, - - getObjectById: function ( id ) { - - return this.getObjectByProperty( 'id', id ); - - }, - - getObjectByName: function ( name ) { - - return this.getObjectByProperty( 'name', name ); - - }, - - getObjectByProperty: function ( name, value ) { - - if ( this[ name ] === value ) return this; - - for ( var i = 0, l = this.children.length; i < l; i ++ ) { - - var child = this.children[ i ]; - var object = child.getObjectByProperty( name, value ); - - if ( object !== undefined ) { - - return object; - - } - - } - - return undefined; - - }, - - getWorldPosition: function ( optionalTarget ) { - - var result = optionalTarget || new Vector3(); - - this.updateMatrixWorld( true ); - - return result.setFromMatrixPosition( this.matrixWorld ); - - }, - - getWorldQuaternion: function () { - - var position = new Vector3(); - var scale = new Vector3(); - - return function getWorldQuaternion( optionalTarget ) { - - var result = optionalTarget || new Quaternion(); - - this.updateMatrixWorld( true ); - - this.matrixWorld.decompose( position, result, scale ); - - return result; - - }; - - }(), - - getWorldRotation: function () { - - var quaternion = new Quaternion(); - - return function getWorldRotation( optionalTarget ) { - - var result = optionalTarget || new Euler(); - - this.getWorldQuaternion( quaternion ); - - return result.setFromQuaternion( quaternion, this.rotation.order, false ); - - }; - - }(), - - getWorldScale: function () { - - var position = new Vector3(); - var quaternion = new Quaternion(); - - return function getWorldScale( optionalTarget ) { - - var result = optionalTarget || new Vector3(); - - this.updateMatrixWorld( true ); - - this.matrixWorld.decompose( position, quaternion, result ); - - return result; - - }; - - }(), - - getWorldDirection: function () { - - var quaternion = new Quaternion(); - - return function getWorldDirection( optionalTarget ) { - - var result = optionalTarget || new Vector3(); - - this.getWorldQuaternion( quaternion ); - - return result.set( 0, 0, 1 ).applyQuaternion( quaternion ); - - }; - - }(), - - raycast: function () {}, - - traverse: function ( callback ) { - - callback( this ); - - var children = this.children; - - for ( var i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].traverse( callback ); - - } - - }, - - traverseVisible: function ( callback ) { - - if ( this.visible === false ) return; - - callback( this ); - - var children = this.children; - - for ( var i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].traverseVisible( callback ); - - } - - }, - - traverseAncestors: function ( callback ) { - - var parent = this.parent; - - if ( parent !== null ) { - - callback( parent ); - - parent.traverseAncestors( callback ); - - } - - }, - - updateMatrix: function () { - - this.matrix.compose( this.position, this.quaternion, this.scale ); - - this.matrixWorldNeedsUpdate = true; - - }, - - updateMatrixWorld: function ( force ) { - - if ( this.matrixAutoUpdate ) this.updateMatrix(); - - if ( this.matrixWorldNeedsUpdate || force ) { - - if ( this.parent === null ) { - - this.matrixWorld.copy( this.matrix ); - - } else { - - this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); - - } - - this.matrixWorldNeedsUpdate = false; - - force = true; - - } - - // update children - - var children = this.children; - - for ( var i = 0, l = children.length; i < l; i ++ ) { - - children[ i ].updateMatrixWorld( force ); - - } - - }, - - toJSON: function ( meta ) { - - // meta is a string when called from JSON.stringify - var isRootObject = ( meta === undefined || typeof meta === 'string' ); - - var output = {}; - - // meta is a hash used to collect geometries, materials. - // not providing it implies that this is the root object - // being serialized. - if ( isRootObject ) { - - // initialize meta obj - meta = { - geometries: {}, - materials: {}, - textures: {}, - images: {} - }; - - output.metadata = { - version: 4.5, - type: 'Object', - generator: 'Object3D.toJSON' - }; - - } - - // standard Object3D serialization - - var object = {}; - - object.uuid = this.uuid; - object.type = this.type; - - if ( this.name !== '' ) object.name = this.name; - if ( this.castShadow === true ) object.castShadow = true; - if ( this.receiveShadow === true ) object.receiveShadow = true; - if ( this.visible === false ) object.visible = false; - if ( JSON.stringify( this.userData ) !== '{}' ) object.userData = this.userData; - - object.matrix = this.matrix.toArray(); - - // - - function serialize( library, element ) { - - if ( library[ element.uuid ] === undefined ) { - - library[ element.uuid ] = element.toJSON( meta ); - - } - - return element.uuid; - - } - - if ( this.geometry !== undefined ) { - - object.geometry = serialize( meta.geometries, this.geometry ); - - } - - if ( this.material !== undefined ) { - - if ( Array.isArray( this.material ) ) { - - var uuids = []; - - for ( var i = 0, l = this.material.length; i < l; i ++ ) { - - uuids.push( serialize( meta.materials, this.material[ i ] ) ); - - } - - object.material = uuids; - - } else { - - object.material = serialize( meta.materials, this.material ); - - } - - } - - // - - if ( this.children.length > 0 ) { - - object.children = []; - - for ( var i = 0; i < this.children.length; i ++ ) { - - object.children.push( this.children[ i ].toJSON( meta ).object ); - - } - - } - - if ( isRootObject ) { - - var geometries = extractFromCache( meta.geometries ); - var materials = extractFromCache( meta.materials ); - var textures = extractFromCache( meta.textures ); - var images = extractFromCache( meta.images ); - - if ( geometries.length > 0 ) output.geometries = geometries; - if ( materials.length > 0 ) output.materials = materials; - if ( textures.length > 0 ) output.textures = textures; - if ( images.length > 0 ) output.images = images; - - } - - output.object = object; - - return output; - - // extract data from the cache hash - // remove metadata on each item - // and return as array - function extractFromCache( cache ) { - - var values = []; - for ( var key in cache ) { - - var data = cache[ key ]; - delete data.metadata; - values.push( data ); - - } - return values; - - } - - }, - - clone: function ( recursive ) { - - return new this.constructor().copy( this, recursive ); - - }, - - copy: function ( source, recursive ) { - - if ( recursive === undefined ) recursive = true; - - this.name = source.name; - - this.up.copy( source.up ); - - this.position.copy( source.position ); - this.quaternion.copy( source.quaternion ); - this.scale.copy( source.scale ); - - this.matrix.copy( source.matrix ); - this.matrixWorld.copy( source.matrixWorld ); - - this.matrixAutoUpdate = source.matrixAutoUpdate; - this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; - - this.layers.mask = source.layers.mask; - this.visible = source.visible; - - this.castShadow = source.castShadow; - this.receiveShadow = source.receiveShadow; - - this.frustumCulled = source.frustumCulled; - this.renderOrder = source.renderOrder; - - this.userData = JSON.parse( JSON.stringify( source.userData ) ); - - if ( recursive === true ) { - - for ( var i = 0; i < source.children.length; i ++ ) { - - var child = source.children[ i ]; - this.add( child.clone() ); - - } - - } - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author mikael emtinger / http://gomo.se/ - * @author WestLangley / http://github.com/WestLangley -*/ - -function Camera() { - - Object3D.call( this ); - - this.type = 'Camera'; - - this.matrixWorldInverse = new Matrix4(); - this.projectionMatrix = new Matrix4(); - -} - -Camera.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: Camera, - - isCamera: true, - - copy: function ( source, recursive ) { - - Object3D.prototype.copy.call( this, source, recursive ); - - this.matrixWorldInverse.copy( source.matrixWorldInverse ); - this.projectionMatrix.copy( source.projectionMatrix ); - - return this; - - }, - - getWorldDirection: function () { - - var quaternion = new Quaternion(); - - return function getWorldDirection( optionalTarget ) { - - var result = optionalTarget || new Vector3(); - - this.getWorldQuaternion( quaternion ); - - return result.set( 0, 0, - 1 ).applyQuaternion( quaternion ); - - }; - - }(), - - updateMatrixWorld: function ( force ) { - - Object3D.prototype.updateMatrixWorld.call( this, force ); - - this.matrixWorldInverse.getInverse( this.matrixWorld ); - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - } - -} ); - -/** - * @author alteredq / http://alteredqualia.com/ - * @author arose / http://github.com/arose - */ - -function OrthographicCamera( left, right, top, bottom, near, far ) { - - Camera.call( this ); - - this.type = 'OrthographicCamera'; - - this.zoom = 1; - this.view = null; - - this.left = left; - this.right = right; - this.top = top; - this.bottom = bottom; - - this.near = ( near !== undefined ) ? near : 0.1; - this.far = ( far !== undefined ) ? far : 2000; - - this.updateProjectionMatrix(); - -} - -OrthographicCamera.prototype = Object.assign( Object.create( Camera.prototype ), { - - constructor: OrthographicCamera, - - isOrthographicCamera: true, - - copy: function ( source, recursive ) { - - Camera.prototype.copy.call( this, source, recursive ); - - this.left = source.left; - this.right = source.right; - this.top = source.top; - this.bottom = source.bottom; - this.near = source.near; - this.far = source.far; - - this.zoom = source.zoom; - this.view = source.view === null ? null : Object.assign( {}, source.view ); - - return this; - - }, - - setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) { - - if ( this.view === null ) { - - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; - - } - - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; - - this.updateProjectionMatrix(); - - }, - - clearViewOffset: function () { - - if ( this.view !== null ) { - - this.view.enabled = false; - - } - - this.updateProjectionMatrix(); - - }, - - updateProjectionMatrix: function () { - - var dx = ( this.right - this.left ) / ( 2 * this.zoom ); - var dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); - var cx = ( this.right + this.left ) / 2; - var cy = ( this.top + this.bottom ) / 2; - - var left = cx - dx; - var right = cx + dx; - var top = cy + dy; - var bottom = cy - dy; - - if ( this.view !== null && this.view.enabled ) { - - var zoomW = this.zoom / ( this.view.width / this.view.fullWidth ); - var zoomH = this.zoom / ( this.view.height / this.view.fullHeight ); - var scaleW = ( this.right - this.left ) / this.view.width; - var scaleH = ( this.top - this.bottom ) / this.view.height; - - left += scaleW * ( this.view.offsetX / zoomW ); - right = left + scaleW * ( this.view.width / zoomW ); - top -= scaleH * ( this.view.offsetY / zoomH ); - bottom = top - scaleH * ( this.view.height / zoomH ); - - } - - this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far ); - - }, - - toJSON: function ( meta ) { - - var data = Object3D.prototype.toJSON.call( this, meta ); - - data.object.zoom = this.zoom; - data.object.left = this.left; - data.object.right = this.right; - data.object.top = this.top; - data.object.bottom = this.bottom; - data.object.near = this.near; - data.object.far = this.far; - - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - - return data; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ - -function Face3( a, b, c, normal, color, materialIndex ) { - - this.a = a; - this.b = b; - this.c = c; - - this.normal = ( normal && normal.isVector3 ) ? normal : new Vector3(); - this.vertexNormals = Array.isArray( normal ) ? normal : []; - - this.color = ( color && color.isColor ) ? color : new Color(); - this.vertexColors = Array.isArray( color ) ? color : []; - - this.materialIndex = materialIndex !== undefined ? materialIndex : 0; - -} - -Object.assign( Face3.prototype, { - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( source ) { - - this.a = source.a; - this.b = source.b; - this.c = source.c; - - this.normal.copy( source.normal ); - this.color.copy( source.color ); - - this.materialIndex = source.materialIndex; - - for ( var i = 0, il = source.vertexNormals.length; i < il; i ++ ) { - - this.vertexNormals[ i ] = source.vertexNormals[ i ].clone(); - - } - - for ( var i = 0, il = source.vertexColors.length; i < il; i ++ ) { - - this.vertexColors[ i ] = source.vertexColors[ i ].clone(); - - } - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author kile / http://kile.stravaganza.org/ - * @author alteredq / http://alteredqualia.com/ - * @author mikael emtinger / http://gomo.se/ - * @author zz85 / http://www.lab4games.net/zz85/blog - * @author bhouston / http://clara.io - */ - -var geometryId = 0; // Geometry uses even numbers as Id - -function Geometry() { - - Object.defineProperty( this, 'id', { value: geometryId += 2 } ); - - this.uuid = _Math.generateUUID(); - - this.name = ''; - this.type = 'Geometry'; - - this.vertices = []; - this.colors = []; - this.faces = []; - this.faceVertexUvs = [[]]; - - this.morphTargets = []; - this.morphNormals = []; - - this.skinWeights = []; - this.skinIndices = []; - - this.lineDistances = []; - - this.boundingBox = null; - this.boundingSphere = null; - - // update flags - - this.elementsNeedUpdate = false; - this.verticesNeedUpdate = false; - this.uvsNeedUpdate = false; - this.normalsNeedUpdate = false; - this.colorsNeedUpdate = false; - this.lineDistancesNeedUpdate = false; - this.groupsNeedUpdate = false; - -} - -Object.assign( Geometry.prototype, EventDispatcher.prototype, { - - isGeometry: true, - - applyMatrix: function ( matrix ) { - - var normalMatrix = new Matrix3().getNormalMatrix( matrix ); - - for ( var i = 0, il = this.vertices.length; i < il; i ++ ) { - - var vertex = this.vertices[ i ]; - vertex.applyMatrix4( matrix ); - - } - - for ( var i = 0, il = this.faces.length; i < il; i ++ ) { - - var face = this.faces[ i ]; - face.normal.applyMatrix3( normalMatrix ).normalize(); - - for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { - - face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize(); - - } - - } - - if ( this.boundingBox !== null ) { - - this.computeBoundingBox(); - - } - - if ( this.boundingSphere !== null ) { - - this.computeBoundingSphere(); - - } - - this.verticesNeedUpdate = true; - this.normalsNeedUpdate = true; - - return this; - - }, - - rotateX: function () { - - // rotate geometry around world x-axis - - var m1 = new Matrix4(); - - return function rotateX( angle ) { - - m1.makeRotationX( angle ); - - this.applyMatrix( m1 ); - - return this; - - }; - - }(), - - rotateY: function () { - - // rotate geometry around world y-axis - - var m1 = new Matrix4(); - - return function rotateY( angle ) { - - m1.makeRotationY( angle ); - - this.applyMatrix( m1 ); - - return this; - - }; - - }(), - - rotateZ: function () { - - // rotate geometry around world z-axis - - var m1 = new Matrix4(); - - return function rotateZ( angle ) { - - m1.makeRotationZ( angle ); - - this.applyMatrix( m1 ); - - return this; - - }; - - }(), - - translate: function () { - - // translate geometry - - var m1 = new Matrix4(); - - return function translate( x, y, z ) { - - m1.makeTranslation( x, y, z ); - - this.applyMatrix( m1 ); - - return this; - - }; - - }(), - - scale: function () { - - // scale geometry - - var m1 = new Matrix4(); - - return function scale( x, y, z ) { - - m1.makeScale( x, y, z ); - - this.applyMatrix( m1 ); - - return this; - - }; - - }(), - - lookAt: function () { - - var obj = new Object3D(); - - return function lookAt( vector ) { - - obj.lookAt( vector ); - - obj.updateMatrix(); - - this.applyMatrix( obj.matrix ); - - }; - - }(), - - fromBufferGeometry: function ( geometry ) { - - var scope = this; - - var indices = geometry.index !== null ? geometry.index.array : undefined; - var attributes = geometry.attributes; - - var positions = attributes.position.array; - var normals = attributes.normal !== undefined ? attributes.normal.array : undefined; - var colors = attributes.color !== undefined ? attributes.color.array : undefined; - var uvs = attributes.uv !== undefined ? attributes.uv.array : undefined; - var uvs2 = attributes.uv2 !== undefined ? attributes.uv2.array : undefined; - - if ( uvs2 !== undefined ) this.faceVertexUvs[ 1 ] = []; - - var tempNormals = []; - var tempUVs = []; - var tempUVs2 = []; - - for ( var i = 0, j = 0; i < positions.length; i += 3, j += 2 ) { - - scope.vertices.push( new Vector3( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ) ); - - if ( normals !== undefined ) { - - tempNormals.push( new Vector3( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ) ); - - } - - if ( colors !== undefined ) { - - scope.colors.push( new Color( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ) ); - - } - - if ( uvs !== undefined ) { - - tempUVs.push( new Vector2( uvs[ j ], uvs[ j + 1 ] ) ); - - } - - if ( uvs2 !== undefined ) { - - tempUVs2.push( new Vector2( uvs2[ j ], uvs2[ j + 1 ] ) ); - - } - - } - - function addFace( a, b, c, materialIndex ) { - - var vertexNormals = normals !== undefined ? [ tempNormals[ a ].clone(), tempNormals[ b ].clone(), tempNormals[ c ].clone() ] : []; - var vertexColors = colors !== undefined ? [ scope.colors[ a ].clone(), scope.colors[ b ].clone(), scope.colors[ c ].clone() ] : []; - - var face = new Face3( a, b, c, vertexNormals, vertexColors, materialIndex ); - - scope.faces.push( face ); - - if ( uvs !== undefined ) { - - scope.faceVertexUvs[ 0 ].push( [ tempUVs[ a ].clone(), tempUVs[ b ].clone(), tempUVs[ c ].clone() ] ); - - } - - if ( uvs2 !== undefined ) { - - scope.faceVertexUvs[ 1 ].push( [ tempUVs2[ a ].clone(), tempUVs2[ b ].clone(), tempUVs2[ c ].clone() ] ); - - } - - } - - var groups = geometry.groups; - - if ( groups.length > 0 ) { - - for ( var i = 0; i < groups.length; i ++ ) { - - var group = groups[ i ]; - - var start = group.start; - var count = group.count; - - for ( var j = start, jl = start + count; j < jl; j += 3 ) { - - if ( indices !== undefined ) { - - addFace( indices[ j ], indices[ j + 1 ], indices[ j + 2 ], group.materialIndex ); - - } else { - - addFace( j, j + 1, j + 2, group.materialIndex ); - - } - - } - - } - - } else { - - if ( indices !== undefined ) { - - for ( var i = 0; i < indices.length; i += 3 ) { - - addFace( indices[ i ], indices[ i + 1 ], indices[ i + 2 ] ); - - } - - } else { - - for ( var i = 0; i < positions.length / 3; i += 3 ) { - - addFace( i, i + 1, i + 2 ); - - } - - } - - } - - this.computeFaceNormals(); - - if ( geometry.boundingBox !== null ) { - - this.boundingBox = geometry.boundingBox.clone(); - - } - - if ( geometry.boundingSphere !== null ) { - - this.boundingSphere = geometry.boundingSphere.clone(); - - } - - return this; - - }, - - center: function () { - - this.computeBoundingBox(); - - var offset = this.boundingBox.getCenter().negate(); - - this.translate( offset.x, offset.y, offset.z ); - - return offset; - - }, - - normalize: function () { - - this.computeBoundingSphere(); - - var center = this.boundingSphere.center; - var radius = this.boundingSphere.radius; - - var s = radius === 0 ? 1 : 1.0 / radius; - - var matrix = new Matrix4(); - matrix.set( - s, 0, 0, - s * center.x, - 0, s, 0, - s * center.y, - 0, 0, s, - s * center.z, - 0, 0, 0, 1 - ); - - this.applyMatrix( matrix ); - - return this; - - }, - - computeFaceNormals: function () { - - var cb = new Vector3(), ab = new Vector3(); - - for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) { - - var face = this.faces[ f ]; - - var vA = this.vertices[ face.a ]; - var vB = this.vertices[ face.b ]; - var vC = this.vertices[ face.c ]; - - cb.subVectors( vC, vB ); - ab.subVectors( vA, vB ); - cb.cross( ab ); - - cb.normalize(); - - face.normal.copy( cb ); - - } - - }, - - computeVertexNormals: function ( areaWeighted ) { - - if ( areaWeighted === undefined ) areaWeighted = true; - - var v, vl, f, fl, face, vertices; - - vertices = new Array( this.vertices.length ); - - for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { - - vertices[ v ] = new Vector3(); - - } - - if ( areaWeighted ) { - - // vertex normals weighted by triangle areas - // http://www.iquilezles.org/www/articles/normals/normals.htm - - var vA, vB, vC; - var cb = new Vector3(), ab = new Vector3(); - - for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - - face = this.faces[ f ]; - - vA = this.vertices[ face.a ]; - vB = this.vertices[ face.b ]; - vC = this.vertices[ face.c ]; - - cb.subVectors( vC, vB ); - ab.subVectors( vA, vB ); - cb.cross( ab ); - - vertices[ face.a ].add( cb ); - vertices[ face.b ].add( cb ); - vertices[ face.c ].add( cb ); - - } - - } else { - - this.computeFaceNormals(); - - for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - - face = this.faces[ f ]; - - vertices[ face.a ].add( face.normal ); - vertices[ face.b ].add( face.normal ); - vertices[ face.c ].add( face.normal ); - - } - - } - - for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { - - vertices[ v ].normalize(); - - } - - for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - - face = this.faces[ f ]; - - var vertexNormals = face.vertexNormals; - - if ( vertexNormals.length === 3 ) { - - vertexNormals[ 0 ].copy( vertices[ face.a ] ); - vertexNormals[ 1 ].copy( vertices[ face.b ] ); - vertexNormals[ 2 ].copy( vertices[ face.c ] ); - - } else { - - vertexNormals[ 0 ] = vertices[ face.a ].clone(); - vertexNormals[ 1 ] = vertices[ face.b ].clone(); - vertexNormals[ 2 ] = vertices[ face.c ].clone(); - - } - - } - - if ( this.faces.length > 0 ) { - - this.normalsNeedUpdate = true; - - } - - }, - - computeFlatVertexNormals: function () { - - var f, fl, face; - - this.computeFaceNormals(); - - for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - - face = this.faces[ f ]; - - var vertexNormals = face.vertexNormals; - - if ( vertexNormals.length === 3 ) { - - vertexNormals[ 0 ].copy( face.normal ); - vertexNormals[ 1 ].copy( face.normal ); - vertexNormals[ 2 ].copy( face.normal ); - - } else { - - vertexNormals[ 0 ] = face.normal.clone(); - vertexNormals[ 1 ] = face.normal.clone(); - vertexNormals[ 2 ] = face.normal.clone(); - - } - - } - - if ( this.faces.length > 0 ) { - - this.normalsNeedUpdate = true; - - } - - }, - - computeMorphNormals: function () { - - var i, il, f, fl, face; - - // save original normals - // - create temp variables on first access - // otherwise just copy (for faster repeated calls) - - for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - - face = this.faces[ f ]; - - if ( ! face.__originalFaceNormal ) { - - face.__originalFaceNormal = face.normal.clone(); - - } else { - - face.__originalFaceNormal.copy( face.normal ); - - } - - if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = []; - - for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) { - - if ( ! face.__originalVertexNormals[ i ] ) { - - face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone(); - - } else { - - face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] ); - - } - - } - - } - - // use temp geometry to compute face and vertex normals for each morph - - var tmpGeo = new Geometry(); - tmpGeo.faces = this.faces; - - for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) { - - // create on first access - - if ( ! this.morphNormals[ i ] ) { - - this.morphNormals[ i ] = {}; - this.morphNormals[ i ].faceNormals = []; - this.morphNormals[ i ].vertexNormals = []; - - var dstNormalsFace = this.morphNormals[ i ].faceNormals; - var dstNormalsVertex = this.morphNormals[ i ].vertexNormals; - - var faceNormal, vertexNormals; - - for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - - faceNormal = new Vector3(); - vertexNormals = { a: new Vector3(), b: new Vector3(), c: new Vector3() }; - - dstNormalsFace.push( faceNormal ); - dstNormalsVertex.push( vertexNormals ); - - } - - } - - var morphNormals = this.morphNormals[ i ]; - - // set vertices to morph target - - tmpGeo.vertices = this.morphTargets[ i ].vertices; - - // compute morph normals - - tmpGeo.computeFaceNormals(); - tmpGeo.computeVertexNormals(); - - // store morph normals - - var faceNormal, vertexNormals; - - for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - - face = this.faces[ f ]; - - faceNormal = morphNormals.faceNormals[ f ]; - vertexNormals = morphNormals.vertexNormals[ f ]; - - faceNormal.copy( face.normal ); - - vertexNormals.a.copy( face.vertexNormals[ 0 ] ); - vertexNormals.b.copy( face.vertexNormals[ 1 ] ); - vertexNormals.c.copy( face.vertexNormals[ 2 ] ); - - } - - } - - // restore original normals - - for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { - - face = this.faces[ f ]; - - face.normal = face.__originalFaceNormal; - face.vertexNormals = face.__originalVertexNormals; - - } - - }, - - computeLineDistances: function () { - - var d = 0; - var vertices = this.vertices; - - for ( var i = 0, il = vertices.length; i < il; i ++ ) { - - if ( i > 0 ) { - - d += vertices[ i ].distanceTo( vertices[ i - 1 ] ); - - } - - this.lineDistances[ i ] = d; - - } - - }, - - computeBoundingBox: function () { - - if ( this.boundingBox === null ) { - - this.boundingBox = new Box3(); - - } - - this.boundingBox.setFromPoints( this.vertices ); - - }, - - computeBoundingSphere: function () { - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } - - this.boundingSphere.setFromPoints( this.vertices ); - - }, - - merge: function ( geometry, matrix, materialIndexOffset ) { - - if ( ! ( geometry && geometry.isGeometry ) ) { - - console.error( 'THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry ); - return; - - } - - var normalMatrix, - vertexOffset = this.vertices.length, - vertices1 = this.vertices, - vertices2 = geometry.vertices, - faces1 = this.faces, - faces2 = geometry.faces, - uvs1 = this.faceVertexUvs[ 0 ], - uvs2 = geometry.faceVertexUvs[ 0 ], - colors1 = this.colors, - colors2 = geometry.colors; - - if ( materialIndexOffset === undefined ) materialIndexOffset = 0; - - if ( matrix !== undefined ) { - - normalMatrix = new Matrix3().getNormalMatrix( matrix ); - - } - - // vertices - - for ( var i = 0, il = vertices2.length; i < il; i ++ ) { - - var vertex = vertices2[ i ]; - - var vertexCopy = vertex.clone(); - - if ( matrix !== undefined ) vertexCopy.applyMatrix4( matrix ); - - vertices1.push( vertexCopy ); - - } - - // colors - - for ( var i = 0, il = colors2.length; i < il; i ++ ) { - - colors1.push( colors2[ i ].clone() ); - - } - - // faces - - for ( i = 0, il = faces2.length; i < il; i ++ ) { - - var face = faces2[ i ], faceCopy, normal, color, - faceVertexNormals = face.vertexNormals, - faceVertexColors = face.vertexColors; - - faceCopy = new Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset ); - faceCopy.normal.copy( face.normal ); - - if ( normalMatrix !== undefined ) { - - faceCopy.normal.applyMatrix3( normalMatrix ).normalize(); - - } - - for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) { - - normal = faceVertexNormals[ j ].clone(); - - if ( normalMatrix !== undefined ) { - - normal.applyMatrix3( normalMatrix ).normalize(); - - } - - faceCopy.vertexNormals.push( normal ); - - } - - faceCopy.color.copy( face.color ); - - for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) { - - color = faceVertexColors[ j ]; - faceCopy.vertexColors.push( color.clone() ); - - } - - faceCopy.materialIndex = face.materialIndex + materialIndexOffset; - - faces1.push( faceCopy ); - - } - - // uvs - - for ( i = 0, il = uvs2.length; i < il; i ++ ) { - - var uv = uvs2[ i ], uvCopy = []; - - if ( uv === undefined ) { - - continue; - - } - - for ( var j = 0, jl = uv.length; j < jl; j ++ ) { - - uvCopy.push( uv[ j ].clone() ); - - } - - uvs1.push( uvCopy ); - - } - - }, - - mergeMesh: function ( mesh ) { - - if ( ! ( mesh && mesh.isMesh ) ) { - - console.error( 'THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh ); - return; - - } - - mesh.matrixAutoUpdate && mesh.updateMatrix(); - - this.merge( mesh.geometry, mesh.matrix ); - - }, - - /* - * Checks for duplicate vertices with hashmap. - * Duplicated vertices are removed - * and faces' vertices are updated. - */ - - mergeVertices: function () { - - var verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique) - var unique = [], changes = []; - - var v, key; - var precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001 - var precision = Math.pow( 10, precisionPoints ); - var i, il, face; - var indices, j, jl; - - for ( i = 0, il = this.vertices.length; i < il; i ++ ) { - - v = this.vertices[ i ]; - key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision ); - - if ( verticesMap[ key ] === undefined ) { - - verticesMap[ key ] = i; - unique.push( this.vertices[ i ] ); - changes[ i ] = unique.length - 1; - - } else { - - //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]); - changes[ i ] = changes[ verticesMap[ key ] ]; - - } - - } - - - // if faces are completely degenerate after merging vertices, we - // have to remove them from the geometry. - var faceIndicesToRemove = []; - - for ( i = 0, il = this.faces.length; i < il; i ++ ) { - - face = this.faces[ i ]; - - face.a = changes[ face.a ]; - face.b = changes[ face.b ]; - face.c = changes[ face.c ]; - - indices = [ face.a, face.b, face.c ]; - - // if any duplicate vertices are found in a Face3 - // we have to remove the face as nothing can be saved - for ( var n = 0; n < 3; n ++ ) { - - if ( indices[ n ] === indices[ ( n + 1 ) % 3 ] ) { - - faceIndicesToRemove.push( i ); - break; - - } - - } - - } - - for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) { - - var idx = faceIndicesToRemove[ i ]; - - this.faces.splice( idx, 1 ); - - for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) { - - this.faceVertexUvs[ j ].splice( idx, 1 ); - - } - - } - - // Use unique set of vertices - - var diff = this.vertices.length - unique.length; - this.vertices = unique; - return diff; - - }, - - setFromPoints: function ( points ) { - - this.vertices = []; - - for ( var i = 0, l = points.length; i < l; i ++ ) { - - var point = points[ i ]; - this.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) ); - - } - - return this; - - }, - - sortFacesByMaterialIndex: function () { - - var faces = this.faces; - var length = faces.length; - - // tag faces - - for ( var i = 0; i < length; i ++ ) { - - faces[ i ]._id = i; - - } - - // sort faces - - function materialIndexSort( a, b ) { - - return a.materialIndex - b.materialIndex; - - } - - faces.sort( materialIndexSort ); - - // sort uvs - - var uvs1 = this.faceVertexUvs[ 0 ]; - var uvs2 = this.faceVertexUvs[ 1 ]; - - var newUvs1, newUvs2; - - if ( uvs1 && uvs1.length === length ) newUvs1 = []; - if ( uvs2 && uvs2.length === length ) newUvs2 = []; - - for ( var i = 0; i < length; i ++ ) { - - var id = faces[ i ]._id; - - if ( newUvs1 ) newUvs1.push( uvs1[ id ] ); - if ( newUvs2 ) newUvs2.push( uvs2[ id ] ); - - } - - if ( newUvs1 ) this.faceVertexUvs[ 0 ] = newUvs1; - if ( newUvs2 ) this.faceVertexUvs[ 1 ] = newUvs2; - - }, - - toJSON: function () { - - var data = { - metadata: { - version: 4.5, - type: 'Geometry', - generator: 'Geometry.toJSON' - } - }; - - // standard Geometry serialization - - data.uuid = this.uuid; - data.type = this.type; - if ( this.name !== '' ) data.name = this.name; - - if ( this.parameters !== undefined ) { - - var parameters = this.parameters; - - for ( var key in parameters ) { - - if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; - - } - - return data; - - } - - var vertices = []; - - for ( var i = 0; i < this.vertices.length; i ++ ) { - - var vertex = this.vertices[ i ]; - vertices.push( vertex.x, vertex.y, vertex.z ); - - } - - var faces = []; - var normals = []; - var normalsHash = {}; - var colors = []; - var colorsHash = {}; - var uvs = []; - var uvsHash = {}; - - for ( var i = 0; i < this.faces.length; i ++ ) { - - var face = this.faces[ i ]; - - var hasMaterial = true; - var hasFaceUv = false; // deprecated - var hasFaceVertexUv = this.faceVertexUvs[ 0 ][ i ] !== undefined; - var hasFaceNormal = face.normal.length() > 0; - var hasFaceVertexNormal = face.vertexNormals.length > 0; - var hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1; - var hasFaceVertexColor = face.vertexColors.length > 0; - - var faceType = 0; - - faceType = setBit( faceType, 0, 0 ); // isQuad - faceType = setBit( faceType, 1, hasMaterial ); - faceType = setBit( faceType, 2, hasFaceUv ); - faceType = setBit( faceType, 3, hasFaceVertexUv ); - faceType = setBit( faceType, 4, hasFaceNormal ); - faceType = setBit( faceType, 5, hasFaceVertexNormal ); - faceType = setBit( faceType, 6, hasFaceColor ); - faceType = setBit( faceType, 7, hasFaceVertexColor ); - - faces.push( faceType ); - faces.push( face.a, face.b, face.c ); - faces.push( face.materialIndex ); - - if ( hasFaceVertexUv ) { - - var faceVertexUvs = this.faceVertexUvs[ 0 ][ i ]; - - faces.push( - getUvIndex( faceVertexUvs[ 0 ] ), - getUvIndex( faceVertexUvs[ 1 ] ), - getUvIndex( faceVertexUvs[ 2 ] ) - ); - - } - - if ( hasFaceNormal ) { - - faces.push( getNormalIndex( face.normal ) ); - - } - - if ( hasFaceVertexNormal ) { - - var vertexNormals = face.vertexNormals; - - faces.push( - getNormalIndex( vertexNormals[ 0 ] ), - getNormalIndex( vertexNormals[ 1 ] ), - getNormalIndex( vertexNormals[ 2 ] ) - ); - - } - - if ( hasFaceColor ) { - - faces.push( getColorIndex( face.color ) ); - - } - - if ( hasFaceVertexColor ) { - - var vertexColors = face.vertexColors; - - faces.push( - getColorIndex( vertexColors[ 0 ] ), - getColorIndex( vertexColors[ 1 ] ), - getColorIndex( vertexColors[ 2 ] ) - ); - - } - - } - - function setBit( value, position, enabled ) { - - return enabled ? value | ( 1 << position ) : value & ( ~ ( 1 << position ) ); - - } - - function getNormalIndex( normal ) { - - var hash = normal.x.toString() + normal.y.toString() + normal.z.toString(); - - if ( normalsHash[ hash ] !== undefined ) { - - return normalsHash[ hash ]; - - } - - normalsHash[ hash ] = normals.length / 3; - normals.push( normal.x, normal.y, normal.z ); - - return normalsHash[ hash ]; - - } - - function getColorIndex( color ) { - - var hash = color.r.toString() + color.g.toString() + color.b.toString(); - - if ( colorsHash[ hash ] !== undefined ) { - - return colorsHash[ hash ]; - - } - - colorsHash[ hash ] = colors.length; - colors.push( color.getHex() ); - - return colorsHash[ hash ]; - - } - - function getUvIndex( uv ) { - - var hash = uv.x.toString() + uv.y.toString(); - - if ( uvsHash[ hash ] !== undefined ) { - - return uvsHash[ hash ]; - - } - - uvsHash[ hash ] = uvs.length / 2; - uvs.push( uv.x, uv.y ); - - return uvsHash[ hash ]; - - } - - data.data = {}; - - data.data.vertices = vertices; - data.data.normals = normals; - if ( colors.length > 0 ) data.data.colors = colors; - if ( uvs.length > 0 ) data.data.uvs = [ uvs ]; // temporal backward compatibility - data.data.faces = faces; - - return data; - - }, - - clone: function () { - - /* - // Handle primitives - - var parameters = this.parameters; - - if ( parameters !== undefined ) { - - var values = []; - - for ( var key in parameters ) { - - values.push( parameters[ key ] ); - - } - - var geometry = Object.create( this.constructor.prototype ); - this.constructor.apply( geometry, values ); - return geometry; - - } - - return new this.constructor().copy( this ); - */ - - return new Geometry().copy( this ); - - }, - - copy: function ( source ) { - - var i, il, j, jl, k, kl; - - // reset - - this.vertices = []; - this.colors = []; - this.faces = []; - this.faceVertexUvs = [[]]; - this.morphTargets = []; - this.morphNormals = []; - this.skinWeights = []; - this.skinIndices = []; - this.lineDistances = []; - this.boundingBox = null; - this.boundingSphere = null; - - // name - - this.name = source.name; - - // vertices - - var vertices = source.vertices; - - for ( i = 0, il = vertices.length; i < il; i ++ ) { - - this.vertices.push( vertices[ i ].clone() ); - - } - - // colors - - var colors = source.colors; - - for ( i = 0, il = colors.length; i < il; i ++ ) { - - this.colors.push( colors[ i ].clone() ); - - } - - // faces - - var faces = source.faces; - - for ( i = 0, il = faces.length; i < il; i ++ ) { - - this.faces.push( faces[ i ].clone() ); - - } - - // face vertex uvs - - for ( i = 0, il = source.faceVertexUvs.length; i < il; i ++ ) { - - var faceVertexUvs = source.faceVertexUvs[ i ]; - - if ( this.faceVertexUvs[ i ] === undefined ) { - - this.faceVertexUvs[ i ] = []; - - } - - for ( j = 0, jl = faceVertexUvs.length; j < jl; j ++ ) { - - var uvs = faceVertexUvs[ j ], uvsCopy = []; - - for ( k = 0, kl = uvs.length; k < kl; k ++ ) { - - var uv = uvs[ k ]; - - uvsCopy.push( uv.clone() ); - - } - - this.faceVertexUvs[ i ].push( uvsCopy ); - - } - - } - - // morph targets - - var morphTargets = source.morphTargets; - - for ( i = 0, il = morphTargets.length; i < il; i ++ ) { - - var morphTarget = {}; - morphTarget.name = morphTargets[ i ].name; - - // vertices - - if ( morphTargets[ i ].vertices !== undefined ) { - - morphTarget.vertices = []; - - for ( j = 0, jl = morphTargets[ i ].vertices.length; j < jl; j ++ ) { - - morphTarget.vertices.push( morphTargets[ i ].vertices[ j ].clone() ); - - } - - } - - // normals - - if ( morphTargets[ i ].normals !== undefined ) { - - morphTarget.normals = []; - - for ( j = 0, jl = morphTargets[ i ].normals.length; j < jl; j ++ ) { - - morphTarget.normals.push( morphTargets[ i ].normals[ j ].clone() ); - - } - - } - - this.morphTargets.push( morphTarget ); - - } - - // morph normals - - var morphNormals = source.morphNormals; - - for ( i = 0, il = morphNormals.length; i < il; i ++ ) { - - var morphNormal = {}; - - // vertex normals - - if ( morphNormals[ i ].vertexNormals !== undefined ) { - - morphNormal.vertexNormals = []; - - for ( j = 0, jl = morphNormals[ i ].vertexNormals.length; j < jl; j ++ ) { - - var srcVertexNormal = morphNormals[ i ].vertexNormals[ j ]; - var destVertexNormal = {}; - - destVertexNormal.a = srcVertexNormal.a.clone(); - destVertexNormal.b = srcVertexNormal.b.clone(); - destVertexNormal.c = srcVertexNormal.c.clone(); - - morphNormal.vertexNormals.push( destVertexNormal ); - - } - - } - - // face normals - - if ( morphNormals[ i ].faceNormals !== undefined ) { - - morphNormal.faceNormals = []; - - for ( j = 0, jl = morphNormals[ i ].faceNormals.length; j < jl; j ++ ) { - - morphNormal.faceNormals.push( morphNormals[ i ].faceNormals[ j ].clone() ); - - } - - } - - this.morphNormals.push( morphNormal ); - - } - - // skin weights - - var skinWeights = source.skinWeights; - - for ( i = 0, il = skinWeights.length; i < il; i ++ ) { - - this.skinWeights.push( skinWeights[ i ].clone() ); - - } - - // skin indices - - var skinIndices = source.skinIndices; - - for ( i = 0, il = skinIndices.length; i < il; i ++ ) { - - this.skinIndices.push( skinIndices[ i ].clone() ); - - } - - // line distances - - var lineDistances = source.lineDistances; - - for ( i = 0, il = lineDistances.length; i < il; i ++ ) { - - this.lineDistances.push( lineDistances[ i ] ); - - } - - // bounding box - - var boundingBox = source.boundingBox; - - if ( boundingBox !== null ) { - - this.boundingBox = boundingBox.clone(); - - } - - // bounding sphere - - var boundingSphere = source.boundingSphere; - - if ( boundingSphere !== null ) { - - this.boundingSphere = boundingSphere.clone(); - - } - - // update flags - - this.elementsNeedUpdate = source.elementsNeedUpdate; - this.verticesNeedUpdate = source.verticesNeedUpdate; - this.uvsNeedUpdate = source.uvsNeedUpdate; - this.normalsNeedUpdate = source.normalsNeedUpdate; - this.colorsNeedUpdate = source.colorsNeedUpdate; - this.lineDistancesNeedUpdate = source.lineDistancesNeedUpdate; - this.groupsNeedUpdate = source.groupsNeedUpdate; - - return this; - - }, - - dispose: function () { - - this.dispatchEvent( { type: 'dispose' } ); - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function BufferAttribute( array, itemSize, normalized ) { - - if ( Array.isArray( array ) ) { - - throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); - - } - - this.uuid = _Math.generateUUID(); - this.name = ''; - - this.array = array; - this.itemSize = itemSize; - this.count = array !== undefined ? array.length / itemSize : 0; - this.normalized = normalized === true; - - this.dynamic = false; - this.updateRange = { offset: 0, count: - 1 }; - - this.onUploadCallback = function () {}; - - this.version = 0; - -} - -Object.defineProperty( BufferAttribute.prototype, 'needsUpdate', { - - set: function ( value ) { - - if ( value === true ) this.version ++; - - } - -} ); - -Object.assign( BufferAttribute.prototype, { - - isBufferAttribute: true, - - setArray: function ( array ) { - - if ( Array.isArray( array ) ) { - - throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); - - } - - this.count = array !== undefined ? array.length / this.itemSize : 0; - this.array = array; - - }, - - setDynamic: function ( value ) { - - this.dynamic = value; - - return this; - - }, - - copy: function ( source ) { - - this.array = new source.array.constructor( source.array ); - this.itemSize = source.itemSize; - this.count = source.count; - this.normalized = source.normalized; - - this.dynamic = source.dynamic; - - return this; - - }, - - copyAt: function ( index1, attribute, index2 ) { - - index1 *= this.itemSize; - index2 *= attribute.itemSize; - - for ( var i = 0, l = this.itemSize; i < l; i ++ ) { - - this.array[ index1 + i ] = attribute.array[ index2 + i ]; - - } - - return this; - - }, - - copyArray: function ( array ) { - - this.array.set( array ); - - return this; - - }, - - copyColorsArray: function ( colors ) { - - var array = this.array, offset = 0; - - for ( var i = 0, l = colors.length; i < l; i ++ ) { - - var color = colors[ i ]; - - if ( color === undefined ) { - - console.warn( 'THREE.BufferAttribute.copyColorsArray(): color is undefined', i ); - color = new Color(); - - } - - array[ offset ++ ] = color.r; - array[ offset ++ ] = color.g; - array[ offset ++ ] = color.b; - - } - - return this; - - }, - - copyIndicesArray: function ( indices ) { - - var array = this.array, offset = 0; - - for ( var i = 0, l = indices.length; i < l; i ++ ) { - - var index = indices[ i ]; - - array[ offset ++ ] = index.a; - array[ offset ++ ] = index.b; - array[ offset ++ ] = index.c; - - } - - return this; - - }, - - copyVector2sArray: function ( vectors ) { - - var array = this.array, offset = 0; - - for ( var i = 0, l = vectors.length; i < l; i ++ ) { - - var vector = vectors[ i ]; - - if ( vector === undefined ) { - - console.warn( 'THREE.BufferAttribute.copyVector2sArray(): vector is undefined', i ); - vector = new Vector2(); - - } - - array[ offset ++ ] = vector.x; - array[ offset ++ ] = vector.y; - - } - - return this; - - }, - - copyVector3sArray: function ( vectors ) { - - var array = this.array, offset = 0; - - for ( var i = 0, l = vectors.length; i < l; i ++ ) { - - var vector = vectors[ i ]; - - if ( vector === undefined ) { - - console.warn( 'THREE.BufferAttribute.copyVector3sArray(): vector is undefined', i ); - vector = new Vector3(); - - } - - array[ offset ++ ] = vector.x; - array[ offset ++ ] = vector.y; - array[ offset ++ ] = vector.z; - - } - - return this; - - }, - - copyVector4sArray: function ( vectors ) { - - var array = this.array, offset = 0; - - for ( var i = 0, l = vectors.length; i < l; i ++ ) { - - var vector = vectors[ i ]; - - if ( vector === undefined ) { - - console.warn( 'THREE.BufferAttribute.copyVector4sArray(): vector is undefined', i ); - vector = new Vector4(); - - } - - array[ offset ++ ] = vector.x; - array[ offset ++ ] = vector.y; - array[ offset ++ ] = vector.z; - array[ offset ++ ] = vector.w; - - } - - return this; - - }, - - set: function ( value, offset ) { - - if ( offset === undefined ) offset = 0; - - this.array.set( value, offset ); - - return this; - - }, - - getX: function ( index ) { - - return this.array[ index * this.itemSize ]; - - }, - - setX: function ( index, x ) { - - this.array[ index * this.itemSize ] = x; - - return this; - - }, - - getY: function ( index ) { - - return this.array[ index * this.itemSize + 1 ]; - - }, - - setY: function ( index, y ) { - - this.array[ index * this.itemSize + 1 ] = y; - - return this; - - }, - - getZ: function ( index ) { - - return this.array[ index * this.itemSize + 2 ]; - - }, - - setZ: function ( index, z ) { - - this.array[ index * this.itemSize + 2 ] = z; - - return this; - - }, - - getW: function ( index ) { - - return this.array[ index * this.itemSize + 3 ]; - - }, - - setW: function ( index, w ) { - - this.array[ index * this.itemSize + 3 ] = w; - - return this; - - }, - - setXY: function ( index, x, y ) { - - index *= this.itemSize; - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - - return this; - - }, - - setXYZ: function ( index, x, y, z ) { - - index *= this.itemSize; - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - - return this; - - }, - - setXYZW: function ( index, x, y, z, w ) { - - index *= this.itemSize; - - this.array[ index + 0 ] = x; - this.array[ index + 1 ] = y; - this.array[ index + 2 ] = z; - this.array[ index + 3 ] = w; - - return this; - - }, - - onUpload: function ( callback ) { - - this.onUploadCallback = callback; - - return this; - - }, - - clone: function () { - - return new this.constructor( this.array, this.itemSize ).copy( this ); - - } - -} ); - -// - -function Int8BufferAttribute( array, itemSize, normalized ) { - - BufferAttribute.call( this, new Int8Array( array ), itemSize, normalized ); - -} - -Int8BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); -Int8BufferAttribute.prototype.constructor = Int8BufferAttribute; - - -function Uint8BufferAttribute( array, itemSize, normalized ) { - - BufferAttribute.call( this, new Uint8Array( array ), itemSize, normalized ); - -} - -Uint8BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); -Uint8BufferAttribute.prototype.constructor = Uint8BufferAttribute; - - -function Uint8ClampedBufferAttribute( array, itemSize, normalized ) { - - BufferAttribute.call( this, new Uint8ClampedArray( array ), itemSize, normalized ); - -} - -Uint8ClampedBufferAttribute.prototype = Object.create( BufferAttribute.prototype ); -Uint8ClampedBufferAttribute.prototype.constructor = Uint8ClampedBufferAttribute; - - -function Int16BufferAttribute( array, itemSize, normalized ) { - - BufferAttribute.call( this, new Int16Array( array ), itemSize, normalized ); - -} - -Int16BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); -Int16BufferAttribute.prototype.constructor = Int16BufferAttribute; - - -function Uint16BufferAttribute( array, itemSize, normalized ) { - - BufferAttribute.call( this, new Uint16Array( array ), itemSize, normalized ); - -} - -Uint16BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); -Uint16BufferAttribute.prototype.constructor = Uint16BufferAttribute; - - -function Int32BufferAttribute( array, itemSize, normalized ) { - - BufferAttribute.call( this, new Int32Array( array ), itemSize, normalized ); - -} - -Int32BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); -Int32BufferAttribute.prototype.constructor = Int32BufferAttribute; - - -function Uint32BufferAttribute( array, itemSize, normalized ) { - - BufferAttribute.call( this, new Uint32Array( array ), itemSize, normalized ); - -} - -Uint32BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); -Uint32BufferAttribute.prototype.constructor = Uint32BufferAttribute; - - -function Float32BufferAttribute( array, itemSize, normalized ) { - - BufferAttribute.call( this, new Float32Array( array ), itemSize, normalized ); - -} - -Float32BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); -Float32BufferAttribute.prototype.constructor = Float32BufferAttribute; - - -function Float64BufferAttribute( array, itemSize, normalized ) { - - BufferAttribute.call( this, new Float64Array( array ), itemSize, normalized ); - -} - -Float64BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); -Float64BufferAttribute.prototype.constructor = Float64BufferAttribute; - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function DirectGeometry() { - - this.indices = []; - this.vertices = []; - this.normals = []; - this.colors = []; - this.uvs = []; - this.uvs2 = []; - - this.groups = []; - - this.morphTargets = {}; - - this.skinWeights = []; - this.skinIndices = []; - - // this.lineDistances = []; - - this.boundingBox = null; - this.boundingSphere = null; - - // update flags - - this.verticesNeedUpdate = false; - this.normalsNeedUpdate = false; - this.colorsNeedUpdate = false; - this.uvsNeedUpdate = false; - this.groupsNeedUpdate = false; - -} - -Object.assign( DirectGeometry.prototype, { - - computeGroups: function ( geometry ) { - - var group; - var groups = []; - var materialIndex = undefined; - - var faces = geometry.faces; - - for ( var i = 0; i < faces.length; i ++ ) { - - var face = faces[ i ]; - - // materials - - if ( face.materialIndex !== materialIndex ) { - - materialIndex = face.materialIndex; - - if ( group !== undefined ) { - - group.count = ( i * 3 ) - group.start; - groups.push( group ); - - } - - group = { - start: i * 3, - materialIndex: materialIndex - }; - - } - - } - - if ( group !== undefined ) { - - group.count = ( i * 3 ) - group.start; - groups.push( group ); - - } - - this.groups = groups; - - }, - - fromGeometry: function ( geometry ) { - - var faces = geometry.faces; - var vertices = geometry.vertices; - var faceVertexUvs = geometry.faceVertexUvs; - - var hasFaceVertexUv = faceVertexUvs[ 0 ] && faceVertexUvs[ 0 ].length > 0; - var hasFaceVertexUv2 = faceVertexUvs[ 1 ] && faceVertexUvs[ 1 ].length > 0; - - // morphs - - var morphTargets = geometry.morphTargets; - var morphTargetsLength = morphTargets.length; - - var morphTargetsPosition; - - if ( morphTargetsLength > 0 ) { - - morphTargetsPosition = []; - - for ( var i = 0; i < morphTargetsLength; i ++ ) { - - morphTargetsPosition[ i ] = []; - - } - - this.morphTargets.position = morphTargetsPosition; - - } - - var morphNormals = geometry.morphNormals; - var morphNormalsLength = morphNormals.length; - - var morphTargetsNormal; - - if ( morphNormalsLength > 0 ) { - - morphTargetsNormal = []; - - for ( var i = 0; i < morphNormalsLength; i ++ ) { - - morphTargetsNormal[ i ] = []; - - } - - this.morphTargets.normal = morphTargetsNormal; - - } - - // skins - - var skinIndices = geometry.skinIndices; - var skinWeights = geometry.skinWeights; - - var hasSkinIndices = skinIndices.length === vertices.length; - var hasSkinWeights = skinWeights.length === vertices.length; - - // - - for ( var i = 0; i < faces.length; i ++ ) { - - var face = faces[ i ]; - - this.vertices.push( vertices[ face.a ], vertices[ face.b ], vertices[ face.c ] ); - - var vertexNormals = face.vertexNormals; - - if ( vertexNormals.length === 3 ) { - - this.normals.push( vertexNormals[ 0 ], vertexNormals[ 1 ], vertexNormals[ 2 ] ); - - } else { - - var normal = face.normal; - - this.normals.push( normal, normal, normal ); - - } - - var vertexColors = face.vertexColors; - - if ( vertexColors.length === 3 ) { - - this.colors.push( vertexColors[ 0 ], vertexColors[ 1 ], vertexColors[ 2 ] ); - - } else { - - var color = face.color; - - this.colors.push( color, color, color ); - - } - - if ( hasFaceVertexUv === true ) { - - var vertexUvs = faceVertexUvs[ 0 ][ i ]; - - if ( vertexUvs !== undefined ) { - - this.uvs.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] ); - - } else { - - console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv ', i ); - - this.uvs.push( new Vector2(), new Vector2(), new Vector2() ); - - } - - } - - if ( hasFaceVertexUv2 === true ) { - - var vertexUvs = faceVertexUvs[ 1 ][ i ]; - - if ( vertexUvs !== undefined ) { - - this.uvs2.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] ); - - } else { - - console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv2 ', i ); - - this.uvs2.push( new Vector2(), new Vector2(), new Vector2() ); - - } - - } - - // morphs - - for ( var j = 0; j < morphTargetsLength; j ++ ) { - - var morphTarget = morphTargets[ j ].vertices; - - morphTargetsPosition[ j ].push( morphTarget[ face.a ], morphTarget[ face.b ], morphTarget[ face.c ] ); - - } - - for ( var j = 0; j < morphNormalsLength; j ++ ) { - - var morphNormal = morphNormals[ j ].vertexNormals[ i ]; - - morphTargetsNormal[ j ].push( morphNormal.a, morphNormal.b, morphNormal.c ); - - } - - // skins - - if ( hasSkinIndices ) { - - this.skinIndices.push( skinIndices[ face.a ], skinIndices[ face.b ], skinIndices[ face.c ] ); - - } - - if ( hasSkinWeights ) { - - this.skinWeights.push( skinWeights[ face.a ], skinWeights[ face.b ], skinWeights[ face.c ] ); - - } - - } - - this.computeGroups( geometry ); - - this.verticesNeedUpdate = geometry.verticesNeedUpdate; - this.normalsNeedUpdate = geometry.normalsNeedUpdate; - this.colorsNeedUpdate = geometry.colorsNeedUpdate; - this.uvsNeedUpdate = geometry.uvsNeedUpdate; - this.groupsNeedUpdate = geometry.groupsNeedUpdate; - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function arrayMax( array ) { - - if ( array.length === 0 ) return - Infinity; - - var max = array[ 0 ]; - - for ( var i = 1, l = array.length; i < l; ++ i ) { - - if ( array[ i ] > max ) max = array[ i ]; - - } - - return max; - -} - -/** - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - */ - -var bufferGeometryId = 1; // BufferGeometry uses odd numbers as Id - -function BufferGeometry() { - - Object.defineProperty( this, 'id', { value: bufferGeometryId += 2 } ); - - this.uuid = _Math.generateUUID(); - - this.name = ''; - this.type = 'BufferGeometry'; - - this.index = null; - this.attributes = {}; - - this.morphAttributes = {}; - - this.groups = []; - - this.boundingBox = null; - this.boundingSphere = null; - - this.drawRange = { start: 0, count: Infinity }; - -} - -Object.assign( BufferGeometry.prototype, EventDispatcher.prototype, { - - isBufferGeometry: true, - - getIndex: function () { - - return this.index; - - }, - - setIndex: function ( index ) { - - if ( Array.isArray( index ) ) { - - this.index = new ( arrayMax( index ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); - - } else { - - this.index = index; - - } - - }, - - addAttribute: function ( name, attribute ) { - - if ( ! ( attribute && attribute.isBufferAttribute ) && ! ( attribute && attribute.isInterleavedBufferAttribute ) ) { - - console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' ); - - this.addAttribute( name, new BufferAttribute( arguments[ 1 ], arguments[ 2 ] ) ); - - return; - - } - - if ( name === 'index' ) { - - console.warn( 'THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.' ); - this.setIndex( attribute ); - - return; - - } - - this.attributes[ name ] = attribute; - - return this; - - }, - - getAttribute: function ( name ) { - - return this.attributes[ name ]; - - }, - - removeAttribute: function ( name ) { - - delete this.attributes[ name ]; - - return this; - - }, - - addGroup: function ( start, count, materialIndex ) { - - this.groups.push( { - - start: start, - count: count, - materialIndex: materialIndex !== undefined ? materialIndex : 0 - - } ); - - }, - - clearGroups: function () { - - this.groups = []; - - }, - - setDrawRange: function ( start, count ) { - - this.drawRange.start = start; - this.drawRange.count = count; - - }, - - applyMatrix: function ( matrix ) { - - var position = this.attributes.position; - - if ( position !== undefined ) { - - matrix.applyToBufferAttribute( position ); - position.needsUpdate = true; - - } - - var normal = this.attributes.normal; - - if ( normal !== undefined ) { - - var normalMatrix = new Matrix3().getNormalMatrix( matrix ); - - normalMatrix.applyToBufferAttribute( normal ); - normal.needsUpdate = true; - - } - - if ( this.boundingBox !== null ) { - - this.computeBoundingBox(); - - } - - if ( this.boundingSphere !== null ) { - - this.computeBoundingSphere(); - - } - - return this; - - }, - - rotateX: function () { - - // rotate geometry around world x-axis - - var m1 = new Matrix4(); - - return function rotateX( angle ) { - - m1.makeRotationX( angle ); - - this.applyMatrix( m1 ); - - return this; - - }; - - }(), - - rotateY: function () { - - // rotate geometry around world y-axis - - var m1 = new Matrix4(); - - return function rotateY( angle ) { - - m1.makeRotationY( angle ); - - this.applyMatrix( m1 ); - - return this; - - }; - - }(), - - rotateZ: function () { - - // rotate geometry around world z-axis - - var m1 = new Matrix4(); - - return function rotateZ( angle ) { - - m1.makeRotationZ( angle ); - - this.applyMatrix( m1 ); - - return this; - - }; - - }(), - - translate: function () { - - // translate geometry - - var m1 = new Matrix4(); - - return function translate( x, y, z ) { - - m1.makeTranslation( x, y, z ); - - this.applyMatrix( m1 ); - - return this; - - }; - - }(), - - scale: function () { - - // scale geometry - - var m1 = new Matrix4(); - - return function scale( x, y, z ) { - - m1.makeScale( x, y, z ); - - this.applyMatrix( m1 ); - - return this; - - }; - - }(), - - lookAt: function () { - - var obj = new Object3D(); - - return function lookAt( vector ) { - - obj.lookAt( vector ); - - obj.updateMatrix(); - - this.applyMatrix( obj.matrix ); - - }; - - }(), - - center: function () { - - this.computeBoundingBox(); - - var offset = this.boundingBox.getCenter().negate(); - - this.translate( offset.x, offset.y, offset.z ); - - return offset; - - }, - - setFromObject: function ( object ) { - - // console.log( 'THREE.BufferGeometry.setFromObject(). Converting', object, this ); - - var geometry = object.geometry; - - if ( object.isPoints || object.isLine ) { - - var positions = new Float32BufferAttribute( geometry.vertices.length * 3, 3 ); - var colors = new Float32BufferAttribute( geometry.colors.length * 3, 3 ); - - this.addAttribute( 'position', positions.copyVector3sArray( geometry.vertices ) ); - this.addAttribute( 'color', colors.copyColorsArray( geometry.colors ) ); - - if ( geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length ) { - - var lineDistances = new Float32BufferAttribute( geometry.lineDistances.length, 1 ); - - this.addAttribute( 'lineDistance', lineDistances.copyArray( geometry.lineDistances ) ); - - } - - if ( geometry.boundingSphere !== null ) { - - this.boundingSphere = geometry.boundingSphere.clone(); - - } - - if ( geometry.boundingBox !== null ) { - - this.boundingBox = geometry.boundingBox.clone(); - - } - - } else if ( object.isMesh ) { - - if ( geometry && geometry.isGeometry ) { - - this.fromGeometry( geometry ); - - } - - } - - return this; - - }, - - setFromPoints: function ( points ) { - - var position = []; - - for ( var i = 0, l = points.length; i < l; i ++ ) { - - var point = points[ i ]; - position.push( point.x, point.y, point.z || 0 ); - - } - - this.addAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); - - return this; - - }, - - updateFromObject: function ( object ) { - - var geometry = object.geometry; - - if ( object.isMesh ) { - - var direct = geometry.__directGeometry; - - if ( geometry.elementsNeedUpdate === true ) { - - direct = undefined; - geometry.elementsNeedUpdate = false; - - } - - if ( direct === undefined ) { - - return this.fromGeometry( geometry ); - - } - - direct.verticesNeedUpdate = geometry.verticesNeedUpdate; - direct.normalsNeedUpdate = geometry.normalsNeedUpdate; - direct.colorsNeedUpdate = geometry.colorsNeedUpdate; - direct.uvsNeedUpdate = geometry.uvsNeedUpdate; - direct.groupsNeedUpdate = geometry.groupsNeedUpdate; - - geometry.verticesNeedUpdate = false; - geometry.normalsNeedUpdate = false; - geometry.colorsNeedUpdate = false; - geometry.uvsNeedUpdate = false; - geometry.groupsNeedUpdate = false; - - geometry = direct; - - } - - var attribute; - - if ( geometry.verticesNeedUpdate === true ) { - - attribute = this.attributes.position; - - if ( attribute !== undefined ) { - - attribute.copyVector3sArray( geometry.vertices ); - attribute.needsUpdate = true; - - } - - geometry.verticesNeedUpdate = false; - - } - - if ( geometry.normalsNeedUpdate === true ) { - - attribute = this.attributes.normal; - - if ( attribute !== undefined ) { - - attribute.copyVector3sArray( geometry.normals ); - attribute.needsUpdate = true; - - } - - geometry.normalsNeedUpdate = false; - - } - - if ( geometry.colorsNeedUpdate === true ) { - - attribute = this.attributes.color; - - if ( attribute !== undefined ) { - - attribute.copyColorsArray( geometry.colors ); - attribute.needsUpdate = true; - - } - - geometry.colorsNeedUpdate = false; - - } - - if ( geometry.uvsNeedUpdate ) { - - attribute = this.attributes.uv; - - if ( attribute !== undefined ) { - - attribute.copyVector2sArray( geometry.uvs ); - attribute.needsUpdate = true; - - } - - geometry.uvsNeedUpdate = false; - - } - - if ( geometry.lineDistancesNeedUpdate ) { - - attribute = this.attributes.lineDistance; - - if ( attribute !== undefined ) { - - attribute.copyArray( geometry.lineDistances ); - attribute.needsUpdate = true; - - } - - geometry.lineDistancesNeedUpdate = false; - - } - - if ( geometry.groupsNeedUpdate ) { - - geometry.computeGroups( object.geometry ); - this.groups = geometry.groups; - - geometry.groupsNeedUpdate = false; - - } - - return this; - - }, - - fromGeometry: function ( geometry ) { - - geometry.__directGeometry = new DirectGeometry().fromGeometry( geometry ); - - return this.fromDirectGeometry( geometry.__directGeometry ); - - }, - - fromDirectGeometry: function ( geometry ) { - - var positions = new Float32Array( geometry.vertices.length * 3 ); - this.addAttribute( 'position', new BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices ) ); - - if ( geometry.normals.length > 0 ) { - - var normals = new Float32Array( geometry.normals.length * 3 ); - this.addAttribute( 'normal', new BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals ) ); - - } - - if ( geometry.colors.length > 0 ) { - - var colors = new Float32Array( geometry.colors.length * 3 ); - this.addAttribute( 'color', new BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors ) ); - - } - - if ( geometry.uvs.length > 0 ) { - - var uvs = new Float32Array( geometry.uvs.length * 2 ); - this.addAttribute( 'uv', new BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs ) ); - - } - - if ( geometry.uvs2.length > 0 ) { - - var uvs2 = new Float32Array( geometry.uvs2.length * 2 ); - this.addAttribute( 'uv2', new BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 ) ); - - } - - if ( geometry.indices.length > 0 ) { - - var TypeArray = arrayMax( geometry.indices ) > 65535 ? Uint32Array : Uint16Array; - var indices = new TypeArray( geometry.indices.length * 3 ); - this.setIndex( new BufferAttribute( indices, 1 ).copyIndicesArray( geometry.indices ) ); - - } - - // groups - - this.groups = geometry.groups; - - // morphs - - for ( var name in geometry.morphTargets ) { - - var array = []; - var morphTargets = geometry.morphTargets[ name ]; - - for ( var i = 0, l = morphTargets.length; i < l; i ++ ) { - - var morphTarget = morphTargets[ i ]; - - var attribute = new Float32BufferAttribute( morphTarget.length * 3, 3 ); - - array.push( attribute.copyVector3sArray( morphTarget ) ); - - } - - this.morphAttributes[ name ] = array; - - } - - // skinning - - if ( geometry.skinIndices.length > 0 ) { - - var skinIndices = new Float32BufferAttribute( geometry.skinIndices.length * 4, 4 ); - this.addAttribute( 'skinIndex', skinIndices.copyVector4sArray( geometry.skinIndices ) ); - - } - - if ( geometry.skinWeights.length > 0 ) { - - var skinWeights = new Float32BufferAttribute( geometry.skinWeights.length * 4, 4 ); - this.addAttribute( 'skinWeight', skinWeights.copyVector4sArray( geometry.skinWeights ) ); - - } - - // - - if ( geometry.boundingSphere !== null ) { - - this.boundingSphere = geometry.boundingSphere.clone(); - - } - - if ( geometry.boundingBox !== null ) { - - this.boundingBox = geometry.boundingBox.clone(); - - } - - return this; - - }, - - computeBoundingBox: function () { - - if ( this.boundingBox === null ) { - - this.boundingBox = new Box3(); - - } - - var position = this.attributes.position; - - if ( position !== undefined ) { - - this.boundingBox.setFromBufferAttribute( position ); - - } else { - - this.boundingBox.makeEmpty(); - - } - - if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { - - console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); - - } - - }, - - computeBoundingSphere: function () { - - var box = new Box3(); - var vector = new Vector3(); - - return function computeBoundingSphere() { - - if ( this.boundingSphere === null ) { - - this.boundingSphere = new Sphere(); - - } - - var position = this.attributes.position; - - if ( position ) { - - var center = this.boundingSphere.center; - - box.setFromBufferAttribute( position ); - box.getCenter( center ); - - // hoping to find a boundingSphere with a radius smaller than the - // boundingSphere of the boundingBox: sqrt(3) smaller in the best case - - var maxRadiusSq = 0; - - for ( var i = 0, il = position.count; i < il; i ++ ) { - - vector.x = position.getX( i ); - vector.y = position.getY( i ); - vector.z = position.getZ( i ); - maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) ); - - } - - this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); - - if ( isNaN( this.boundingSphere.radius ) ) { - - console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); - - } - - } - - }; - - }(), - - computeFaceNormals: function () { - - // backwards compatibility - - }, - - computeVertexNormals: function () { - - var index = this.index; - var attributes = this.attributes; - var groups = this.groups; - - if ( attributes.position ) { - - var positions = attributes.position.array; - - if ( attributes.normal === undefined ) { - - this.addAttribute( 'normal', new BufferAttribute( new Float32Array( positions.length ), 3 ) ); - - } else { - - // reset existing normals to zero - - var array = attributes.normal.array; - - for ( var i = 0, il = array.length; i < il; i ++ ) { - - array[ i ] = 0; - - } - - } - - var normals = attributes.normal.array; - - var vA, vB, vC; - var pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); - var cb = new Vector3(), ab = new Vector3(); - - // indexed elements - - if ( index ) { - - var indices = index.array; - - if ( groups.length === 0 ) { - - this.addGroup( 0, indices.length ); - - } - - for ( var j = 0, jl = groups.length; j < jl; ++ j ) { - - var group = groups[ j ]; - - var start = group.start; - var count = group.count; - - for ( var i = start, il = start + count; i < il; i += 3 ) { - - vA = indices[ i + 0 ] * 3; - vB = indices[ i + 1 ] * 3; - vC = indices[ i + 2 ] * 3; - - pA.fromArray( positions, vA ); - pB.fromArray( positions, vB ); - pC.fromArray( positions, vC ); - - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); - - normals[ vA ] += cb.x; - normals[ vA + 1 ] += cb.y; - normals[ vA + 2 ] += cb.z; - - normals[ vB ] += cb.x; - normals[ vB + 1 ] += cb.y; - normals[ vB + 2 ] += cb.z; - - normals[ vC ] += cb.x; - normals[ vC + 1 ] += cb.y; - normals[ vC + 2 ] += cb.z; - - } - - } - - } else { - - // non-indexed elements (unconnected triangle soup) - - for ( var i = 0, il = positions.length; i < il; i += 9 ) { - - pA.fromArray( positions, i ); - pB.fromArray( positions, i + 3 ); - pC.fromArray( positions, i + 6 ); - - cb.subVectors( pC, pB ); - ab.subVectors( pA, pB ); - cb.cross( ab ); - - normals[ i ] = cb.x; - normals[ i + 1 ] = cb.y; - normals[ i + 2 ] = cb.z; - - normals[ i + 3 ] = cb.x; - normals[ i + 4 ] = cb.y; - normals[ i + 5 ] = cb.z; - - normals[ i + 6 ] = cb.x; - normals[ i + 7 ] = cb.y; - normals[ i + 8 ] = cb.z; - - } - - } - - this.normalizeNormals(); - - attributes.normal.needsUpdate = true; - - } - - }, - - merge: function ( geometry, offset ) { - - if ( ! ( geometry && geometry.isBufferGeometry ) ) { - - console.error( 'THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry ); - return; - - } - - if ( offset === undefined ) offset = 0; - - var attributes = this.attributes; - - for ( var key in attributes ) { - - if ( geometry.attributes[ key ] === undefined ) continue; - - var attribute1 = attributes[ key ]; - var attributeArray1 = attribute1.array; - - var attribute2 = geometry.attributes[ key ]; - var attributeArray2 = attribute2.array; - - var attributeSize = attribute2.itemSize; - - for ( var i = 0, j = attributeSize * offset; i < attributeArray2.length; i ++, j ++ ) { - - attributeArray1[ j ] = attributeArray2[ i ]; - - } - - } - - return this; - - }, - - normalizeNormals: function () { - - var vector = new Vector3(); - - return function normalizeNormals() { - - var normals = this.attributes.normal; - - for ( var i = 0, il = normals.count; i < il; i ++ ) { - - vector.x = normals.getX( i ); - vector.y = normals.getY( i ); - vector.z = normals.getZ( i ); - - vector.normalize(); - - normals.setXYZ( i, vector.x, vector.y, vector.z ); - - } - - }; - - }(), - - toNonIndexed: function () { - - if ( this.index === null ) { - - console.warn( 'THREE.BufferGeometry.toNonIndexed(): Geometry is already non-indexed.' ); - return this; - - } - - var geometry2 = new BufferGeometry(); - - var indices = this.index.array; - var attributes = this.attributes; - - for ( var name in attributes ) { - - var attribute = attributes[ name ]; - - var array = attribute.array; - var itemSize = attribute.itemSize; - - var array2 = new array.constructor( indices.length * itemSize ); - - var index = 0, index2 = 0; - - for ( var i = 0, l = indices.length; i < l; i ++ ) { - - index = indices[ i ] * itemSize; - - for ( var j = 0; j < itemSize; j ++ ) { - - array2[ index2 ++ ] = array[ index ++ ]; - - } - - } - - geometry2.addAttribute( name, new BufferAttribute( array2, itemSize ) ); - - } - - return geometry2; - - }, - - toJSON: function () { - - var data = { - metadata: { - version: 4.5, - type: 'BufferGeometry', - generator: 'BufferGeometry.toJSON' - } - }; - - // standard BufferGeometry serialization - - data.uuid = this.uuid; - data.type = this.type; - if ( this.name !== '' ) data.name = this.name; - - if ( this.parameters !== undefined ) { - - var parameters = this.parameters; - - for ( var key in parameters ) { - - if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; - - } - - return data; - - } - - data.data = { attributes: {} }; - - var index = this.index; - - if ( index !== null ) { - - var array = Array.prototype.slice.call( index.array ); - - data.data.index = { - type: index.array.constructor.name, - array: array - }; - - } - - var attributes = this.attributes; - - for ( var key in attributes ) { - - var attribute = attributes[ key ]; - - var array = Array.prototype.slice.call( attribute.array ); - - data.data.attributes[ key ] = { - itemSize: attribute.itemSize, - type: attribute.array.constructor.name, - array: array, - normalized: attribute.normalized - }; - - } - - var groups = this.groups; - - if ( groups.length > 0 ) { - - data.data.groups = JSON.parse( JSON.stringify( groups ) ); - - } - - var boundingSphere = this.boundingSphere; - - if ( boundingSphere !== null ) { - - data.data.boundingSphere = { - center: boundingSphere.center.toArray(), - radius: boundingSphere.radius - }; - - } - - return data; - - }, - - clone: function () { - - /* - // Handle primitives - - var parameters = this.parameters; - - if ( parameters !== undefined ) { - - var values = []; - - for ( var key in parameters ) { - - values.push( parameters[ key ] ); - - } - - var geometry = Object.create( this.constructor.prototype ); - this.constructor.apply( geometry, values ); - return geometry; - - } - - return new this.constructor().copy( this ); - */ - - return new BufferGeometry().copy( this ); - - }, - - copy: function ( source ) { - - var name, i, l; - - // reset - - this.index = null; - this.attributes = {}; - this.morphAttributes = {}; - this.groups = []; - this.boundingBox = null; - this.boundingSphere = null; - - // name - - this.name = source.name; - - // index - - var index = source.index; - - if ( index !== null ) { - - this.setIndex( index.clone() ); - - } - - // attributes - - var attributes = source.attributes; - - for ( name in attributes ) { - - var attribute = attributes[ name ]; - this.addAttribute( name, attribute.clone() ); - - } - - // morph attributes - - var morphAttributes = source.morphAttributes; - - for ( name in morphAttributes ) { - - var array = []; - var morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes - - for ( i = 0, l = morphAttribute.length; i < l; i ++ ) { - - array.push( morphAttribute[ i ].clone() ); - - } - - this.morphAttributes[ name ] = array; - - } - - // groups - - var groups = source.groups; - - for ( i = 0, l = groups.length; i < l; i ++ ) { - - var group = groups[ i ]; - this.addGroup( group.start, group.count, group.materialIndex ); - - } - - // bounding box - - var boundingBox = source.boundingBox; - - if ( boundingBox !== null ) { - - this.boundingBox = boundingBox.clone(); - - } - - // bounding sphere - - var boundingSphere = source.boundingSphere; - - if ( boundingSphere !== null ) { - - this.boundingSphere = boundingSphere.clone(); - - } - - // draw range - - this.drawRange.start = source.drawRange.start; - this.drawRange.count = source.drawRange.count; - - return this; - - }, - - dispose: function () { - - this.dispatchEvent( { type: 'dispose' } ); - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author Mugen87 / https://github.com/Mugen87 - */ - -// BoxGeometry - -function BoxGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) { - - Geometry.call( this ); - - this.type = 'BoxGeometry'; - - this.parameters = { - width: width, - height: height, - depth: depth, - widthSegments: widthSegments, - heightSegments: heightSegments, - depthSegments: depthSegments - }; - - this.fromBufferGeometry( new BoxBufferGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) ); - this.mergeVertices(); - -} - -BoxGeometry.prototype = Object.create( Geometry.prototype ); -BoxGeometry.prototype.constructor = BoxGeometry; - -// BoxBufferGeometry - -function BoxBufferGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) { - - BufferGeometry.call( this ); - - this.type = 'BoxBufferGeometry'; - - this.parameters = { - width: width, - height: height, - depth: depth, - widthSegments: widthSegments, - heightSegments: heightSegments, - depthSegments: depthSegments - }; - - var scope = this; - - width = width || 1; - height = height || 1; - depth = depth || 1; - - // segments - - widthSegments = Math.floor( widthSegments ) || 1; - heightSegments = Math.floor( heightSegments ) || 1; - depthSegments = Math.floor( depthSegments ) || 1; - - // buffers - - var indices = []; - var vertices = []; - var normals = []; - var uvs = []; - - // helper variables - - var numberOfVertices = 0; - var groupStart = 0; - - // build each side of the box geometry - - buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px - buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx - buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py - buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny - buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz - buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { - - var segmentWidth = width / gridX; - var segmentHeight = height / gridY; - - var widthHalf = width / 2; - var heightHalf = height / 2; - var depthHalf = depth / 2; - - var gridX1 = gridX + 1; - var gridY1 = gridY + 1; - - var vertexCounter = 0; - var groupCount = 0; - - var ix, iy; - - var vector = new Vector3(); - - // generate vertices, normals and uvs - - for ( iy = 0; iy < gridY1; iy ++ ) { - - var y = iy * segmentHeight - heightHalf; - - for ( ix = 0; ix < gridX1; ix ++ ) { - - var x = ix * segmentWidth - widthHalf; - - // set values to correct vector component - - vector[ u ] = x * udir; - vector[ v ] = y * vdir; - vector[ w ] = depthHalf; - - // now apply vector to vertex buffer - - vertices.push( vector.x, vector.y, vector.z ); - - // set values to correct vector component - - vector[ u ] = 0; - vector[ v ] = 0; - vector[ w ] = depth > 0 ? 1 : - 1; - - // now apply vector to normal buffer - - normals.push( vector.x, vector.y, vector.z ); - - // uvs - - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); - - // counters - - vertexCounter += 1; - - } - - } - - // indices - - // 1. you need three indices to draw a single face - // 2. a single segment consists of two faces - // 3. so we need to generate six (2*3) indices per segment - - for ( iy = 0; iy < gridY; iy ++ ) { - - for ( ix = 0; ix < gridX; ix ++ ) { - - var a = numberOfVertices + ix + gridX1 * iy; - var b = numberOfVertices + ix + gridX1 * ( iy + 1 ); - var c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); - var d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - // increase counter - - groupCount += 6; - - } - - } - - // add a group to the geometry. this will ensure multi material support - - scope.addGroup( groupStart, groupCount, materialIndex ); - - // calculate new start value for groups - - groupStart += groupCount; - - // update total number of vertices - - numberOfVertices += vertexCounter; - - } - -} - -BoxBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -BoxBufferGeometry.prototype.constructor = BoxBufferGeometry; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author Mugen87 / https://github.com/Mugen87 - */ - -// PlaneGeometry - -function PlaneGeometry( width, height, widthSegments, heightSegments ) { - - Geometry.call( this ); - - this.type = 'PlaneGeometry'; - - this.parameters = { - width: width, - height: height, - widthSegments: widthSegments, - heightSegments: heightSegments - }; - - this.fromBufferGeometry( new PlaneBufferGeometry( width, height, widthSegments, heightSegments ) ); - this.mergeVertices(); - -} - -PlaneGeometry.prototype = Object.create( Geometry.prototype ); -PlaneGeometry.prototype.constructor = PlaneGeometry; - -// PlaneBufferGeometry - -function PlaneBufferGeometry( width, height, widthSegments, heightSegments ) { - - BufferGeometry.call( this ); - - this.type = 'PlaneBufferGeometry'; - - this.parameters = { - width: width, - height: height, - widthSegments: widthSegments, - heightSegments: heightSegments - }; - - width = width || 1; - height = height || 1; - - var width_half = width / 2; - var height_half = height / 2; - - var gridX = Math.floor( widthSegments ) || 1; - var gridY = Math.floor( heightSegments ) || 1; - - var gridX1 = gridX + 1; - var gridY1 = gridY + 1; - - var segment_width = width / gridX; - var segment_height = height / gridY; - - var ix, iy; - - // buffers - - var indices = []; - var vertices = []; - var normals = []; - var uvs = []; - - // generate vertices, normals and uvs - - for ( iy = 0; iy < gridY1; iy ++ ) { - - var y = iy * segment_height - height_half; - - for ( ix = 0; ix < gridX1; ix ++ ) { - - var x = ix * segment_width - width_half; - - vertices.push( x, - y, 0 ); - - normals.push( 0, 0, 1 ); - - uvs.push( ix / gridX ); - uvs.push( 1 - ( iy / gridY ) ); - - } - - } - - // indices - - for ( iy = 0; iy < gridY; iy ++ ) { - - for ( ix = 0; ix < gridX; ix ++ ) { - - var a = ix + gridX1 * iy; - var b = ix + gridX1 * ( iy + 1 ); - var c = ( ix + 1 ) + gridX1 * ( iy + 1 ); - var d = ( ix + 1 ) + gridX1 * iy; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - -} - -PlaneBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -PlaneBufferGeometry.prototype.constructor = PlaneBufferGeometry; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * - * parameters = { - * color: , - * opacity: , - * map: new THREE.Texture( ), - * - * lightMap: new THREE.Texture( ), - * lightMapIntensity: - * - * aoMap: new THREE.Texture( ), - * aoMapIntensity: - * - * specularMap: new THREE.Texture( ), - * - * alphaMap: new THREE.Texture( ), - * - * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), - * combine: THREE.Multiply, - * reflectivity: , - * refractionRatio: , - * - * depthTest: , - * depthWrite: , - * - * wireframe: , - * wireframeLinewidth: , - * - * skinning: , - * morphTargets: - * } - */ - -function MeshBasicMaterial( parameters ) { - - Material.call( this ); - - this.type = 'MeshBasicMaterial'; - - this.color = new Color( 0xffffff ); // emissive - - this.map = null; - - this.lightMap = null; - this.lightMapIntensity = 1.0; - - this.aoMap = null; - this.aoMapIntensity = 1.0; - - this.specularMap = null; - - this.alphaMap = null; - - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; - - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.skinning = false; - this.morphTargets = false; - - this.lights = false; - - this.setValues( parameters ); - -} - -MeshBasicMaterial.prototype = Object.create( Material.prototype ); -MeshBasicMaterial.prototype.constructor = MeshBasicMaterial; - -MeshBasicMaterial.prototype.isMeshBasicMaterial = true; - -MeshBasicMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.specularMap = source.specularMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.skinning = source.skinning; - this.morphTargets = source.morphTargets; - - return this; - -}; - -/** - * @author alteredq / http://alteredqualia.com/ - * - * parameters = { - * defines: { "label" : "value" }, - * uniforms: { "parameter1": { value: 1.0 }, "parameter2": { value2: 2 } }, - * - * fragmentShader: , - * vertexShader: , - * - * wireframe: , - * wireframeLinewidth: , - * - * lights: , - * - * skinning: , - * morphTargets: , - * morphNormals: - * } - */ - -function ShaderMaterial( parameters ) { - - Material.call( this ); - - this.type = 'ShaderMaterial'; - - this.defines = {}; - this.uniforms = {}; - - this.vertexShader = 'void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}'; - this.fragmentShader = 'void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}'; - - this.linewidth = 1; - - this.wireframe = false; - this.wireframeLinewidth = 1; - - this.fog = false; // set to use scene fog - this.lights = false; // set to use scene lights - this.clipping = false; // set to use user-defined clipping planes - - this.skinning = false; // set to use skinning attribute streams - this.morphTargets = false; // set to use morph targets - this.morphNormals = false; // set to use morph normals - - this.extensions = { - derivatives: false, // set to use derivatives - fragDepth: false, // set to use fragment depth values - drawBuffers: false, // set to use draw buffers - shaderTextureLOD: false // set to use shader texture LOD - }; - - // When rendered geometry doesn't include these attributes but the material does, - // use these default values in WebGL. This avoids errors when buffer data is missing. - this.defaultAttributeValues = { - 'color': [ 1, 1, 1 ], - 'uv': [ 0, 0 ], - 'uv2': [ 0, 0 ] - }; - - this.index0AttributeName = undefined; - - if ( parameters !== undefined ) { - - if ( parameters.attributes !== undefined ) { - - console.error( 'THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.' ); - - } - - this.setValues( parameters ); - - } - -} - -ShaderMaterial.prototype = Object.create( Material.prototype ); -ShaderMaterial.prototype.constructor = ShaderMaterial; - -ShaderMaterial.prototype.isShaderMaterial = true; - -ShaderMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.fragmentShader = source.fragmentShader; - this.vertexShader = source.vertexShader; - - this.uniforms = UniformsUtils.clone( source.uniforms ); - - this.defines = source.defines; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - this.lights = source.lights; - this.clipping = source.clipping; - - this.skinning = source.skinning; - - this.morphTargets = source.morphTargets; - this.morphNormals = source.morphNormals; - - this.extensions = source.extensions; - - return this; - -}; - -ShaderMaterial.prototype.toJSON = function ( meta ) { - - var data = Material.prototype.toJSON.call( this, meta ); - - data.uniforms = this.uniforms; - data.vertexShader = this.vertexShader; - data.fragmentShader = this.fragmentShader; - - return data; - -}; - -/** - * @author bhouston / http://clara.io - */ - -function Ray( origin, direction ) { - - this.origin = ( origin !== undefined ) ? origin : new Vector3(); - this.direction = ( direction !== undefined ) ? direction : new Vector3(); - -} - -Object.assign( Ray.prototype, { - - set: function ( origin, direction ) { - - this.origin.copy( origin ); - this.direction.copy( direction ); - - return this; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( ray ) { - - this.origin.copy( ray.origin ); - this.direction.copy( ray.direction ); - - return this; - - }, - - at: function ( t, optionalTarget ) { - - var result = optionalTarget || new Vector3(); - - return result.copy( this.direction ).multiplyScalar( t ).add( this.origin ); - - }, - - lookAt: function ( v ) { - - this.direction.copy( v ).sub( this.origin ).normalize(); - - return this; - - }, - - recast: function () { - - var v1 = new Vector3(); - - return function recast( t ) { - - this.origin.copy( this.at( t, v1 ) ); - - return this; - - }; - - }(), - - closestPointToPoint: function ( point, optionalTarget ) { - - var result = optionalTarget || new Vector3(); - result.subVectors( point, this.origin ); - var directionDistance = result.dot( this.direction ); - - if ( directionDistance < 0 ) { - - return result.copy( this.origin ); - - } - - return result.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); - - }, - - distanceToPoint: function ( point ) { - - return Math.sqrt( this.distanceSqToPoint( point ) ); - - }, - - distanceSqToPoint: function () { - - var v1 = new Vector3(); - - return function distanceSqToPoint( point ) { - - var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction ); - - // point behind the ray - - if ( directionDistance < 0 ) { - - return this.origin.distanceToSquared( point ); - - } - - v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); - - return v1.distanceToSquared( point ); - - }; - - }(), - - distanceSqToSegment: function () { - - var segCenter = new Vector3(); - var segDir = new Vector3(); - var diff = new Vector3(); - - return function distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { - - // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h - // It returns the min distance between the ray and the segment - // defined by v0 and v1 - // It can also set two optional targets : - // - The closest point on the ray - // - The closest point on the segment - - segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 ); - segDir.copy( v1 ).sub( v0 ).normalize(); - diff.copy( this.origin ).sub( segCenter ); - - var segExtent = v0.distanceTo( v1 ) * 0.5; - var a01 = - this.direction.dot( segDir ); - var b0 = diff.dot( this.direction ); - var b1 = - diff.dot( segDir ); - var c = diff.lengthSq(); - var det = Math.abs( 1 - a01 * a01 ); - var s0, s1, sqrDist, extDet; - - if ( det > 0 ) { - - // The ray and segment are not parallel. - - s0 = a01 * b1 - b0; - s1 = a01 * b0 - b1; - extDet = segExtent * det; - - if ( s0 >= 0 ) { - - if ( s1 >= - extDet ) { - - if ( s1 <= extDet ) { - - // region 0 - // Minimum at interior points of ray and segment. - - var invDet = 1 / det; - s0 *= invDet; - s1 *= invDet; - sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; - - } else { - - // region 1 - - s1 = segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } else { - - // region 5 - - s1 = - segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } else { - - if ( s1 <= - extDet ) { - - // region 4 - - s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); - s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } else if ( s1 <= extDet ) { - - // region 3 - - s0 = 0; - s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = s1 * ( s1 + 2 * b1 ) + c; - - } else { - - // region 2 - - s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); - s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - } - - } else { - - // Ray and segment are parallel. - - s1 = ( a01 > 0 ) ? - segExtent : segExtent; - s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); - sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; - - } - - if ( optionalPointOnRay ) { - - optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin ); - - } - - if ( optionalPointOnSegment ) { - - optionalPointOnSegment.copy( segDir ).multiplyScalar( s1 ).add( segCenter ); - - } - - return sqrDist; - - }; - - }(), - - intersectSphere: function () { - - var v1 = new Vector3(); - - return function intersectSphere( sphere, optionalTarget ) { - - v1.subVectors( sphere.center, this.origin ); - var tca = v1.dot( this.direction ); - var d2 = v1.dot( v1 ) - tca * tca; - var radius2 = sphere.radius * sphere.radius; - - if ( d2 > radius2 ) return null; - - var thc = Math.sqrt( radius2 - d2 ); - - // t0 = first intersect point - entrance on front of sphere - var t0 = tca - thc; - - // t1 = second intersect point - exit point on back of sphere - var t1 = tca + thc; - - // test to see if both t0 and t1 are behind the ray - if so, return null - if ( t0 < 0 && t1 < 0 ) return null; - - // test to see if t0 is behind the ray: - // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, - // in order to always return an intersect point that is in front of the ray. - if ( t0 < 0 ) return this.at( t1, optionalTarget ); - - // else t0 is in front of the ray, so return the first collision point scaled by t0 - return this.at( t0, optionalTarget ); - - }; - - }(), - - intersectsSphere: function ( sphere ) { - - return this.distanceToPoint( sphere.center ) <= sphere.radius; - - }, - - distanceToPlane: function ( plane ) { - - var denominator = plane.normal.dot( this.direction ); - - if ( denominator === 0 ) { - - // line is coplanar, return origin - if ( plane.distanceToPoint( this.origin ) === 0 ) { - - return 0; - - } - - // Null is preferable to undefined since undefined means.... it is undefined - - return null; - - } - - var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; - - // Return if the ray never intersects the plane - - return t >= 0 ? t : null; - - }, - - intersectPlane: function ( plane, optionalTarget ) { - - var t = this.distanceToPlane( plane ); - - if ( t === null ) { - - return null; - - } - - return this.at( t, optionalTarget ); - - }, - - intersectsPlane: function ( plane ) { - - // check if the ray lies on the plane first - - var distToPoint = plane.distanceToPoint( this.origin ); - - if ( distToPoint === 0 ) { - - return true; - - } - - var denominator = plane.normal.dot( this.direction ); - - if ( denominator * distToPoint < 0 ) { - - return true; - - } - - // ray origin is behind the plane (and is pointing behind it) - - return false; - - }, - - intersectBox: function ( box, optionalTarget ) { - - var tmin, tmax, tymin, tymax, tzmin, tzmax; - - var invdirx = 1 / this.direction.x, - invdiry = 1 / this.direction.y, - invdirz = 1 / this.direction.z; - - var origin = this.origin; - - if ( invdirx >= 0 ) { - - tmin = ( box.min.x - origin.x ) * invdirx; - tmax = ( box.max.x - origin.x ) * invdirx; - - } else { - - tmin = ( box.max.x - origin.x ) * invdirx; - tmax = ( box.min.x - origin.x ) * invdirx; - - } - - if ( invdiry >= 0 ) { - - tymin = ( box.min.y - origin.y ) * invdiry; - tymax = ( box.max.y - origin.y ) * invdiry; - - } else { - - tymin = ( box.max.y - origin.y ) * invdiry; - tymax = ( box.min.y - origin.y ) * invdiry; - - } - - if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; - - // These lines also handle the case where tmin or tmax is NaN - // (result of 0 * Infinity). x !== x returns true if x is NaN - - if ( tymin > tmin || tmin !== tmin ) tmin = tymin; - - if ( tymax < tmax || tmax !== tmax ) tmax = tymax; - - if ( invdirz >= 0 ) { - - tzmin = ( box.min.z - origin.z ) * invdirz; - tzmax = ( box.max.z - origin.z ) * invdirz; - - } else { - - tzmin = ( box.max.z - origin.z ) * invdirz; - tzmax = ( box.min.z - origin.z ) * invdirz; - - } - - if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; - - if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; - - if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; - - //return point closest to the ray (positive side) - - if ( tmax < 0 ) return null; - - return this.at( tmin >= 0 ? tmin : tmax, optionalTarget ); - - }, - - intersectsBox: ( function () { - - var v = new Vector3(); - - return function intersectsBox( box ) { - - return this.intersectBox( box, v ) !== null; - - }; - - } )(), - - intersectTriangle: function () { - - // Compute the offset origin, edges, and normal. - var diff = new Vector3(); - var edge1 = new Vector3(); - var edge2 = new Vector3(); - var normal = new Vector3(); - - return function intersectTriangle( a, b, c, backfaceCulling, optionalTarget ) { - - // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h - - edge1.subVectors( b, a ); - edge2.subVectors( c, a ); - normal.crossVectors( edge1, edge2 ); - - // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, - // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by - // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) - // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) - // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) - var DdN = this.direction.dot( normal ); - var sign; - - if ( DdN > 0 ) { - - if ( backfaceCulling ) return null; - sign = 1; - - } else if ( DdN < 0 ) { - - sign = - 1; - DdN = - DdN; - - } else { - - return null; - - } - - diff.subVectors( this.origin, a ); - var DdQxE2 = sign * this.direction.dot( edge2.crossVectors( diff, edge2 ) ); - - // b1 < 0, no intersection - if ( DdQxE2 < 0 ) { - - return null; - - } - - var DdE1xQ = sign * this.direction.dot( edge1.cross( diff ) ); - - // b2 < 0, no intersection - if ( DdE1xQ < 0 ) { - - return null; - - } - - // b1+b2 > 1, no intersection - if ( DdQxE2 + DdE1xQ > DdN ) { - - return null; - - } - - // Line intersects triangle, check if ray does. - var QdN = - sign * diff.dot( normal ); - - // t < 0, no intersection - if ( QdN < 0 ) { - - return null; - - } - - // Ray intersects triangle. - return this.at( QdN / DdN, optionalTarget ); - - }; - - }(), - - applyMatrix4: function ( matrix4 ) { - - this.origin.applyMatrix4( matrix4 ); - this.direction.transformDirection( matrix4 ); - - return this; - - }, - - equals: function ( ray ) { - - return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); - - } - -} ); - -/** - * @author bhouston / http://clara.io - */ - -function Line3( start, end ) { - - this.start = ( start !== undefined ) ? start : new Vector3(); - this.end = ( end !== undefined ) ? end : new Vector3(); - -} - -Object.assign( Line3.prototype, { - - set: function ( start, end ) { - - this.start.copy( start ); - this.end.copy( end ); - - return this; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( line ) { - - this.start.copy( line.start ); - this.end.copy( line.end ); - - return this; - - }, - - getCenter: function ( optionalTarget ) { - - var result = optionalTarget || new Vector3(); - return result.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); - - }, - - delta: function ( optionalTarget ) { - - var result = optionalTarget || new Vector3(); - return result.subVectors( this.end, this.start ); - - }, - - distanceSq: function () { - - return this.start.distanceToSquared( this.end ); - - }, - - distance: function () { - - return this.start.distanceTo( this.end ); - - }, - - at: function ( t, optionalTarget ) { - - var result = optionalTarget || new Vector3(); - - return this.delta( result ).multiplyScalar( t ).add( this.start ); - - }, - - closestPointToPointParameter: function () { - - var startP = new Vector3(); - var startEnd = new Vector3(); - - return function closestPointToPointParameter( point, clampToLine ) { - - startP.subVectors( point, this.start ); - startEnd.subVectors( this.end, this.start ); - - var startEnd2 = startEnd.dot( startEnd ); - var startEnd_startP = startEnd.dot( startP ); - - var t = startEnd_startP / startEnd2; - - if ( clampToLine ) { - - t = _Math.clamp( t, 0, 1 ); - - } - - return t; - - }; - - }(), - - closestPointToPoint: function ( point, clampToLine, optionalTarget ) { - - var t = this.closestPointToPointParameter( point, clampToLine ); - - var result = optionalTarget || new Vector3(); - - return this.delta( result ).multiplyScalar( t ).add( this.start ); - - }, - - applyMatrix4: function ( matrix ) { - - this.start.applyMatrix4( matrix ); - this.end.applyMatrix4( matrix ); - - return this; - - }, - - equals: function ( line ) { - - return line.start.equals( this.start ) && line.end.equals( this.end ); - - } - -} ); - -/** - * @author bhouston / http://clara.io - * @author mrdoob / http://mrdoob.com/ - */ - -function Triangle( a, b, c ) { - - this.a = ( a !== undefined ) ? a : new Vector3(); - this.b = ( b !== undefined ) ? b : new Vector3(); - this.c = ( c !== undefined ) ? c : new Vector3(); - -} - -Object.assign( Triangle, { - - normal: function () { - - var v0 = new Vector3(); - - return function normal( a, b, c, optionalTarget ) { - - var result = optionalTarget || new Vector3(); - - result.subVectors( c, b ); - v0.subVectors( a, b ); - result.cross( v0 ); - - var resultLengthSq = result.lengthSq(); - if ( resultLengthSq > 0 ) { - - return result.multiplyScalar( 1 / Math.sqrt( resultLengthSq ) ); - - } - - return result.set( 0, 0, 0 ); - - }; - - }(), - - // static/instance method to calculate barycentric coordinates - // based on: http://www.blackpawn.com/texts/pointinpoly/default.html - barycoordFromPoint: function () { - - var v0 = new Vector3(); - var v1 = new Vector3(); - var v2 = new Vector3(); - - return function barycoordFromPoint( point, a, b, c, optionalTarget ) { - - v0.subVectors( c, a ); - v1.subVectors( b, a ); - v2.subVectors( point, a ); - - var dot00 = v0.dot( v0 ); - var dot01 = v0.dot( v1 ); - var dot02 = v0.dot( v2 ); - var dot11 = v1.dot( v1 ); - var dot12 = v1.dot( v2 ); - - var denom = ( dot00 * dot11 - dot01 * dot01 ); - - var result = optionalTarget || new Vector3(); - - // collinear or singular triangle - if ( denom === 0 ) { - - // arbitrary location outside of triangle? - // not sure if this is the best idea, maybe should be returning undefined - return result.set( - 2, - 1, - 1 ); - - } - - var invDenom = 1 / denom; - var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; - var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; - - // barycentric coordinates must always sum to 1 - return result.set( 1 - u - v, v, u ); - - }; - - }(), - - containsPoint: function () { - - var v1 = new Vector3(); - - return function containsPoint( point, a, b, c ) { - - var result = Triangle.barycoordFromPoint( point, a, b, c, v1 ); - - return ( result.x >= 0 ) && ( result.y >= 0 ) && ( ( result.x + result.y ) <= 1 ); - - }; - - }() - -} ); - -Object.assign( Triangle.prototype, { - - set: function ( a, b, c ) { - - this.a.copy( a ); - this.b.copy( b ); - this.c.copy( c ); - - return this; - - }, - - setFromPointsAndIndices: function ( points, i0, i1, i2 ) { - - this.a.copy( points[ i0 ] ); - this.b.copy( points[ i1 ] ); - this.c.copy( points[ i2 ] ); - - return this; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( triangle ) { - - this.a.copy( triangle.a ); - this.b.copy( triangle.b ); - this.c.copy( triangle.c ); - - return this; - - }, - - area: function () { - - var v0 = new Vector3(); - var v1 = new Vector3(); - - return function area() { - - v0.subVectors( this.c, this.b ); - v1.subVectors( this.a, this.b ); - - return v0.cross( v1 ).length() * 0.5; - - }; - - }(), - - midpoint: function ( optionalTarget ) { - - var result = optionalTarget || new Vector3(); - return result.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); - - }, - - normal: function ( optionalTarget ) { - - return Triangle.normal( this.a, this.b, this.c, optionalTarget ); - - }, - - plane: function ( optionalTarget ) { - - var result = optionalTarget || new Plane(); - - return result.setFromCoplanarPoints( this.a, this.b, this.c ); - - }, - - barycoordFromPoint: function ( point, optionalTarget ) { - - return Triangle.barycoordFromPoint( point, this.a, this.b, this.c, optionalTarget ); - - }, - - containsPoint: function ( point ) { - - return Triangle.containsPoint( point, this.a, this.b, this.c ); - - }, - - closestPointToPoint: function () { - - var plane = new Plane(); - var edgeList = [ new Line3(), new Line3(), new Line3() ]; - var projectedPoint = new Vector3(); - var closestPoint = new Vector3(); - - return function closestPointToPoint( point, optionalTarget ) { - - var result = optionalTarget || new Vector3(); - var minDistance = Infinity; - - // project the point onto the plane of the triangle - - plane.setFromCoplanarPoints( this.a, this.b, this.c ); - plane.projectPoint( point, projectedPoint ); - - // check if the projection lies within the triangle - - if ( this.containsPoint( projectedPoint ) === true ) { - - // if so, this is the closest point - - result.copy( projectedPoint ); - - } else { - - // if not, the point falls outside the triangle. the result is the closest point to the triangle's edges or vertices - - edgeList[ 0 ].set( this.a, this.b ); - edgeList[ 1 ].set( this.b, this.c ); - edgeList[ 2 ].set( this.c, this.a ); - - for ( var i = 0; i < edgeList.length; i ++ ) { - - edgeList[ i ].closestPointToPoint( projectedPoint, true, closestPoint ); - - var distance = projectedPoint.distanceToSquared( closestPoint ); - - if ( distance < minDistance ) { - - minDistance = distance; - - result.copy( closestPoint ); - - } - - } - - } - - return result; - - }; - - }(), - - equals: function ( triangle ) { - - return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * @author mikael emtinger / http://gomo.se/ - * @author jonobr1 / http://jonobr1.com/ - */ - -function Mesh( geometry, material ) { - - Object3D.call( this ); - - this.type = 'Mesh'; - - this.geometry = geometry !== undefined ? geometry : new BufferGeometry(); - this.material = material !== undefined ? material : new MeshBasicMaterial( { color: Math.random() * 0xffffff } ); - - this.drawMode = TrianglesDrawMode; - - this.updateMorphTargets(); - -} - -Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: Mesh, - - isMesh: true, - - setDrawMode: function ( value ) { - - this.drawMode = value; - - }, - - copy: function ( source ) { - - Object3D.prototype.copy.call( this, source ); - - this.drawMode = source.drawMode; - - if ( source.morphTargetInfluences !== undefined ) { - - this.morphTargetInfluences = source.morphTargetInfluences.slice(); - - } - - if ( source.morphTargetDictionary !== undefined ) { - - this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); - - } - - return this; - - }, - - updateMorphTargets: function () { - - var geometry = this.geometry; - var m, ml, name; - - if ( geometry.isBufferGeometry ) { - - var morphAttributes = geometry.morphAttributes; - var keys = Object.keys( morphAttributes ); - - if ( keys.length > 0 ) { - - var morphAttribute = morphAttributes[ keys[ 0 ] ]; - - if ( morphAttribute !== undefined ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( m = 0, ml = morphAttribute.length; m < ml; m ++ ) { - - name = morphAttribute[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - } else { - - var morphTargets = geometry.morphTargets; - - if ( morphTargets !== undefined && morphTargets.length > 0 ) { - - this.morphTargetInfluences = []; - this.morphTargetDictionary = {}; - - for ( m = 0, ml = morphTargets.length; m < ml; m ++ ) { - - name = morphTargets[ m ].name || String( m ); - - this.morphTargetInfluences.push( 0 ); - this.morphTargetDictionary[ name ] = m; - - } - - } - - } - - }, - - raycast: ( function () { - - var inverseMatrix = new Matrix4(); - var ray = new Ray(); - var sphere = new Sphere(); - - var vA = new Vector3(); - var vB = new Vector3(); - var vC = new Vector3(); - - var tempA = new Vector3(); - var tempB = new Vector3(); - var tempC = new Vector3(); - - var uvA = new Vector2(); - var uvB = new Vector2(); - var uvC = new Vector2(); - - var barycoord = new Vector3(); - - var intersectionPoint = new Vector3(); - var intersectionPointWorld = new Vector3(); - - function uvIntersection( point, p1, p2, p3, uv1, uv2, uv3 ) { - - Triangle.barycoordFromPoint( point, p1, p2, p3, barycoord ); - - uv1.multiplyScalar( barycoord.x ); - uv2.multiplyScalar( barycoord.y ); - uv3.multiplyScalar( barycoord.z ); - - uv1.add( uv2 ).add( uv3 ); - - return uv1.clone(); - - } - - function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) { - - var intersect; - - if ( material.side === BackSide ) { - - intersect = ray.intersectTriangle( pC, pB, pA, true, point ); - - } else { - - intersect = ray.intersectTriangle( pA, pB, pC, material.side !== DoubleSide, point ); - - } - - if ( intersect === null ) return null; - - intersectionPointWorld.copy( point ); - intersectionPointWorld.applyMatrix4( object.matrixWorld ); - - var distance = raycaster.ray.origin.distanceTo( intersectionPointWorld ); - - if ( distance < raycaster.near || distance > raycaster.far ) return null; - - return { - distance: distance, - point: intersectionPointWorld.clone(), - object: object - }; - - } - - function checkBufferGeometryIntersection( object, raycaster, ray, position, uv, a, b, c ) { - - vA.fromBufferAttribute( position, a ); - vB.fromBufferAttribute( position, b ); - vC.fromBufferAttribute( position, c ); - - var intersection = checkIntersection( object, object.material, raycaster, ray, vA, vB, vC, intersectionPoint ); - - if ( intersection ) { - - if ( uv ) { - - uvA.fromBufferAttribute( uv, a ); - uvB.fromBufferAttribute( uv, b ); - uvC.fromBufferAttribute( uv, c ); - - intersection.uv = uvIntersection( intersectionPoint, vA, vB, vC, uvA, uvB, uvC ); - - } - - intersection.face = new Face3( a, b, c, Triangle.normal( vA, vB, vC ) ); - intersection.faceIndex = a; - - } - - return intersection; - - } - - return function raycast( raycaster, intersects ) { - - var geometry = this.geometry; - var material = this.material; - var matrixWorld = this.matrixWorld; - - if ( material === undefined ) return; - - // Checking boundingSphere distance to ray - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - sphere.copy( geometry.boundingSphere ); - sphere.applyMatrix4( matrixWorld ); - - if ( raycaster.ray.intersectsSphere( sphere ) === false ) return; - - // - - inverseMatrix.getInverse( matrixWorld ); - ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); - - // Check boundingBox before continuing - - if ( geometry.boundingBox !== null ) { - - if ( ray.intersectsBox( geometry.boundingBox ) === false ) return; - - } - - var intersection; - - if ( geometry.isBufferGeometry ) { - - var a, b, c; - var index = geometry.index; - var position = geometry.attributes.position; - var uv = geometry.attributes.uv; - var i, l; - - if ( index !== null ) { - - // indexed buffer geometry - - for ( i = 0, l = index.count; i < l; i += 3 ) { - - a = index.getX( i ); - b = index.getX( i + 1 ); - c = index.getX( i + 2 ); - - intersection = checkBufferGeometryIntersection( this, raycaster, ray, position, uv, a, b, c ); - - if ( intersection ) { - - intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indices buffer semantics - intersects.push( intersection ); - - } - - } - - } else if ( position !== undefined ) { - - // non-indexed buffer geometry - - for ( i = 0, l = position.count; i < l; i += 3 ) { - - a = i; - b = i + 1; - c = i + 2; - - intersection = checkBufferGeometryIntersection( this, raycaster, ray, position, uv, a, b, c ); - - if ( intersection ) { - - intersection.index = a; // triangle number in positions buffer semantics - intersects.push( intersection ); - - } - - } - - } - - } else if ( geometry.isGeometry ) { - - var fvA, fvB, fvC; - var isMultiMaterial = Array.isArray( material ); - - var vertices = geometry.vertices; - var faces = geometry.faces; - var uvs; - - var faceVertexUvs = geometry.faceVertexUvs[ 0 ]; - if ( faceVertexUvs.length > 0 ) uvs = faceVertexUvs; - - for ( var f = 0, fl = faces.length; f < fl; f ++ ) { - - var face = faces[ f ]; - var faceMaterial = isMultiMaterial ? material[ face.materialIndex ] : material; - - if ( faceMaterial === undefined ) continue; - - fvA = vertices[ face.a ]; - fvB = vertices[ face.b ]; - fvC = vertices[ face.c ]; - - if ( faceMaterial.morphTargets === true ) { - - var morphTargets = geometry.morphTargets; - var morphInfluences = this.morphTargetInfluences; - - vA.set( 0, 0, 0 ); - vB.set( 0, 0, 0 ); - vC.set( 0, 0, 0 ); - - for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) { - - var influence = morphInfluences[ t ]; - - if ( influence === 0 ) continue; - - var targets = morphTargets[ t ].vertices; - - vA.addScaledVector( tempA.subVectors( targets[ face.a ], fvA ), influence ); - vB.addScaledVector( tempB.subVectors( targets[ face.b ], fvB ), influence ); - vC.addScaledVector( tempC.subVectors( targets[ face.c ], fvC ), influence ); - - } - - vA.add( fvA ); - vB.add( fvB ); - vC.add( fvC ); - - fvA = vA; - fvB = vB; - fvC = vC; - - } - - intersection = checkIntersection( this, faceMaterial, raycaster, ray, fvA, fvB, fvC, intersectionPoint ); - - if ( intersection ) { - - if ( uvs && uvs[ f ] ) { - - var uvs_f = uvs[ f ]; - uvA.copy( uvs_f[ 0 ] ); - uvB.copy( uvs_f[ 1 ] ); - uvC.copy( uvs_f[ 2 ] ); - - intersection.uv = uvIntersection( intersectionPoint, fvA, fvB, fvC, uvA, uvB, uvC ); - - } - - intersection.face = face; - intersection.faceIndex = f; - intersects.push( intersection ); - - } - - } - - } - - }; - - }() ), - - clone: function () { - - return new this.constructor( this.geometry, this.material ).copy( this ); - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLBackground( renderer, state, geometries, premultipliedAlpha ) { - - var clearColor = new Color( 0x000000 ); - var clearAlpha = 0; - - var planeCamera, planeMesh; - var boxMesh; - - function render( renderList, scene, camera, forceClear ) { - - var background = scene.background; - - if ( background === null ) { - - setClear( clearColor, clearAlpha ); - - } else if ( background && background.isColor ) { - - setClear( background, 1 ); - forceClear = true; - - } - - if ( renderer.autoClear || forceClear ) { - - renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); - - } - - if ( background && background.isCubeTexture ) { - - if ( boxMesh === undefined ) { - - boxMesh = new Mesh( - new BoxBufferGeometry( 1, 1, 1 ), - new ShaderMaterial( { - uniforms: ShaderLib.cube.uniforms, - vertexShader: ShaderLib.cube.vertexShader, - fragmentShader: ShaderLib.cube.fragmentShader, - side: BackSide, - depthTest: true, - depthWrite: false, - fog: false - } ) - ); - - boxMesh.geometry.removeAttribute( 'normal' ); - boxMesh.geometry.removeAttribute( 'uv' ); - - boxMesh.onBeforeRender = function ( renderer, scene, camera ) { - - this.matrixWorld.copyPosition( camera.matrixWorld ); - - }; - - geometries.update( boxMesh.geometry ); - - } - - boxMesh.material.uniforms.tCube.value = background; - - renderList.push( boxMesh, boxMesh.geometry, boxMesh.material, 0, null ); - - } else if ( background && background.isTexture ) { - - if ( planeCamera === undefined ) { - - planeCamera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); - - planeMesh = new Mesh( - new PlaneBufferGeometry( 2, 2 ), - new MeshBasicMaterial( { depthTest: false, depthWrite: false, fog: false } ) - ); - - geometries.update( planeMesh.geometry ); - - } - - planeMesh.material.map = background; - - // TODO Push this to renderList - - renderer.renderBufferDirect( planeCamera, null, planeMesh.geometry, planeMesh.material, planeMesh, null ); - - } - - } - - function setClear( color, alpha ) { - - state.buffers.color.setClear( color.r, color.g, color.b, alpha, premultipliedAlpha ); - - } - - return { - - getClearColor: function () { - - return clearColor; - - }, - setClearColor: function ( color, alpha ) { - - clearColor.set( color ); - clearAlpha = alpha !== undefined ? alpha : 1; - setClear( clearColor, clearAlpha ); - - }, - getClearAlpha: function () { - - return clearAlpha; - - }, - setClearAlpha: function ( alpha ) { - - clearAlpha = alpha; - setClear( clearColor, clearAlpha ); - - }, - render: render - - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function painterSortStable( a, b ) { - - if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } else if ( a.program && b.program && a.program !== b.program ) { - - return a.program.id - b.program.id; - - } else if ( a.material.id !== b.material.id ) { - - return a.material.id - b.material.id; - - } else if ( a.z !== b.z ) { - - return a.z - b.z; - - } else { - - return a.id - b.id; - - } - -} - -function reversePainterSortStable( a, b ) { - - if ( a.renderOrder !== b.renderOrder ) { - - return a.renderOrder - b.renderOrder; - - } if ( a.z !== b.z ) { - - return b.z - a.z; - - } else { - - return a.id - b.id; - - } - -} - -function WebGLRenderList() { - - var renderItems = []; - var renderItemsIndex = 0; - - var opaque = []; - var transparent = []; - - function init() { - - renderItemsIndex = 0; - - opaque.length = 0; - transparent.length = 0; - - } - - function push( object, geometry, material, z, group ) { - - var renderItem = renderItems[ renderItemsIndex ]; - - if ( renderItem === undefined ) { - - renderItem = { - id: object.id, - object: object, - geometry: geometry, - material: material, - program: material.program, - renderOrder: object.renderOrder, - z: z, - group: group - }; - - renderItems[ renderItemsIndex ] = renderItem; - - } else { - - renderItem.id = object.id; - renderItem.object = object; - renderItem.geometry = geometry; - renderItem.material = material; - renderItem.program = material.program; - renderItem.renderOrder = object.renderOrder; - renderItem.z = z; - renderItem.group = group; - - } - - ( material.transparent === true ? transparent : opaque ).push( renderItem ); - - renderItemsIndex ++; - - } - - function sort() { - - if ( opaque.length > 1 ) opaque.sort( painterSortStable ); - if ( transparent.length > 1 ) transparent.sort( reversePainterSortStable ); - - } - - return { - opaque: opaque, - transparent: transparent, - - init: init, - push: push, - - sort: sort - }; - -} - -function WebGLRenderLists() { - - var lists = {}; - - function get( scene, camera ) { - - var hash = scene.id + ',' + camera.id; - var list = lists[ hash ]; - - if ( list === undefined ) { - - // console.log( 'THREE.WebGLRenderLists:', hash ); - - list = new WebGLRenderList(); - lists[ hash ] = list; - - } - - return list; - - } - - function dispose() { - - lists = {}; - - } - - return { - get: get, - dispose: dispose - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function absNumericalSort( a, b ) { - - return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] ); - -} - -function WebGLMorphtargets( gl ) { - - var influencesList = {}; - var morphInfluences = new Float32Array( 8 ); - - function update( object, geometry, material, program ) { - - var objectInfluences = object.morphTargetInfluences; - - var length = objectInfluences.length; - - var influences = influencesList[ geometry.id ]; - - if ( influences === undefined ) { - - // initialise list - - influences = []; - - for ( var i = 0; i < length; i ++ ) { - - influences[ i ] = [ i, 0 ]; - - } - - influencesList[ geometry.id ] = influences; - - } - - var morphTargets = material.morphTargets && geometry.morphAttributes.position; - var morphNormals = material.morphNormals && geometry.morphAttributes.normal; - - // Remove current morphAttributes - - for ( var i = 0; i < length; i ++ ) { - - var influence = influences[ i ]; - - if ( influence[ 1 ] !== 0 ) { - - if ( morphTargets ) geometry.removeAttribute( 'morphTarget' + i ); - if ( morphNormals ) geometry.removeAttribute( 'morphNormal' + i ); - - } - - } - - // Collect influences - - for ( var i = 0; i < length; i ++ ) { - - var influence = influences[ i ]; - - influence[ 0 ] = i; - influence[ 1 ] = objectInfluences[ i ]; - - } - - influences.sort( absNumericalSort ); - - // Add morphAttributes - - for ( var i = 0; i < 8; i ++ ) { - - var influence = influences[ i ]; - - if ( influence ) { - - var index = influence[ 0 ]; - var value = influence[ 1 ]; - - if ( value ) { - - if ( morphTargets ) geometry.addAttribute( 'morphTarget' + i, morphTargets[ index ] ); - if ( morphNormals ) geometry.addAttribute( 'morphNormal' + i, morphNormals[ index ] ); - - morphInfluences[ i ] = value; - continue; - - } - - } - - morphInfluences[ i ] = 0; - - } - - program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences ); - - } - - return { - - update: update - - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLIndexedBufferRenderer( gl, extensions, infoRender ) { - - var mode; - - function setMode( value ) { - - mode = value; - - } - - var type, bytesPerElement; - - function setIndex( value ) { - - type = value.type; - bytesPerElement = value.bytesPerElement; - - } - - function render( start, count ) { - - gl.drawElements( mode, count, type, start * bytesPerElement ); - - infoRender.calls ++; - infoRender.vertices += count; - - if ( mode === gl.TRIANGLES ) infoRender.faces += count / 3; - else if ( mode === gl.POINTS ) infoRender.points += count; - - } - - function renderInstances( geometry, start, count ) { - - var extension = extensions.get( 'ANGLE_instanced_arrays' ); - - if ( extension === null ) { - - console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; - - } - - extension.drawElementsInstancedANGLE( mode, count, type, start * bytesPerElement, geometry.maxInstancedCount ); - - infoRender.calls ++; - infoRender.vertices += count * geometry.maxInstancedCount; - - if ( mode === gl.TRIANGLES ) infoRender.faces += geometry.maxInstancedCount * count / 3; - else if ( mode === gl.POINTS ) infoRender.points += geometry.maxInstancedCount * count; - - } - - // - - this.setMode = setMode; - this.setIndex = setIndex; - this.render = render; - this.renderInstances = renderInstances; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLBufferRenderer( gl, extensions, infoRender ) { - - var mode; - - function setMode( value ) { - - mode = value; - - } - - function render( start, count ) { - - gl.drawArrays( mode, start, count ); - - infoRender.calls ++; - infoRender.vertices += count; - - if ( mode === gl.TRIANGLES ) infoRender.faces += count / 3; - else if ( mode === gl.POINTS ) infoRender.points += count; - - } - - function renderInstances( geometry, start, count ) { - - var extension = extensions.get( 'ANGLE_instanced_arrays' ); - - if ( extension === null ) { - - console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; - - } - - var position = geometry.attributes.position; - - if ( position.isInterleavedBufferAttribute ) { - - count = position.data.count; - - extension.drawArraysInstancedANGLE( mode, 0, count, geometry.maxInstancedCount ); - - } else { - - extension.drawArraysInstancedANGLE( mode, start, count, geometry.maxInstancedCount ); - - } - - infoRender.calls ++; - infoRender.vertices += count * geometry.maxInstancedCount; - - if ( mode === gl.TRIANGLES ) infoRender.faces += geometry.maxInstancedCount * count / 3; - else if ( mode === gl.POINTS ) infoRender.points += geometry.maxInstancedCount * count; - - } - - // - - this.setMode = setMode; - this.render = render; - this.renderInstances = renderInstances; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLGeometries( gl, attributes, infoMemory ) { - - var geometries = {}; - var wireframeAttributes = {}; - - function onGeometryDispose( event ) { - - var geometry = event.target; - var buffergeometry = geometries[ geometry.id ]; - - if ( buffergeometry.index !== null ) { - - attributes.remove( buffergeometry.index ); - - } - - for ( var name in buffergeometry.attributes ) { - - attributes.remove( buffergeometry.attributes[ name ] ); - - } - - geometry.removeEventListener( 'dispose', onGeometryDispose ); - - delete geometries[ geometry.id ]; - - // TODO Remove duplicate code - - var attribute = wireframeAttributes[ geometry.id ]; - - if ( attribute ) { - - attributes.remove( attribute ); - delete wireframeAttributes[ geometry.id ]; - - } - - attribute = wireframeAttributes[ buffergeometry.id ]; - - if ( attribute ) { - - attributes.remove( attribute ); - delete wireframeAttributes[ buffergeometry.id ]; - - } - - // - - infoMemory.geometries --; - - } - - function get( object, geometry ) { - - var buffergeometry = geometries[ geometry.id ]; - - if ( buffergeometry ) return buffergeometry; - - geometry.addEventListener( 'dispose', onGeometryDispose ); - - if ( geometry.isBufferGeometry ) { - - buffergeometry = geometry; - - } else if ( geometry.isGeometry ) { - - if ( geometry._bufferGeometry === undefined ) { - - geometry._bufferGeometry = new BufferGeometry().setFromObject( object ); - - } - - buffergeometry = geometry._bufferGeometry; - - } - - geometries[ geometry.id ] = buffergeometry; - - infoMemory.geometries ++; - - return buffergeometry; - - } - - function update( geometry ) { - - var index = geometry.index; - var geometryAttributes = geometry.attributes; - - if ( index !== null ) { - - attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); - - } - - for ( var name in geometryAttributes ) { - - attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); - - } - - // morph targets - - var morphAttributes = geometry.morphAttributes; - - for ( var name in morphAttributes ) { - - var array = morphAttributes[ name ]; - - for ( var i = 0, l = array.length; i < l; i ++ ) { - - attributes.update( array[ i ], gl.ARRAY_BUFFER ); - - } - - } - - } - - function getWireframeAttribute( geometry ) { - - var attribute = wireframeAttributes[ geometry.id ]; - - if ( attribute ) return attribute; - - var indices = []; - - var geometryIndex = geometry.index; - var geometryAttributes = geometry.attributes; - - // console.time( 'wireframe' ); - - if ( geometryIndex !== null ) { - - var array = geometryIndex.array; - - for ( var i = 0, l = array.length; i < l; i += 3 ) { - - var a = array[ i + 0 ]; - var b = array[ i + 1 ]; - var c = array[ i + 2 ]; - - indices.push( a, b, b, c, c, a ); - - } - - } else { - - var array = geometryAttributes.position.array; - - for ( var i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { - - var a = i + 0; - var b = i + 1; - var c = i + 2; - - indices.push( a, b, b, c, c, a ); - - } - - } - - // console.timeEnd( 'wireframe' ); - - attribute = new ( arrayMax( indices ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); - - attributes.update( attribute, gl.ELEMENT_ARRAY_BUFFER ); - - wireframeAttributes[ geometry.id ] = attribute; - - return attribute; - - } - - return { - - get: get, - update: update, - - getWireframeAttribute: getWireframeAttribute - - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function UniformsCache() { - - var lights = {}; - - return { - - get: function ( light ) { - - if ( lights[ light.id ] !== undefined ) { - - return lights[ light.id ]; - - } - - var uniforms; - - switch ( light.type ) { - - case 'DirectionalLight': - uniforms = { - direction: new Vector3(), - color: new Color(), - - shadow: false, - shadowBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; - - case 'SpotLight': - uniforms = { - position: new Vector3(), - direction: new Vector3(), - color: new Color(), - distance: 0, - coneCos: 0, - penumbraCos: 0, - decay: 0, - - shadow: false, - shadowBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2() - }; - break; - - case 'PointLight': - uniforms = { - position: new Vector3(), - color: new Color(), - distance: 0, - decay: 0, - - shadow: false, - shadowBias: 0, - shadowRadius: 1, - shadowMapSize: new Vector2(), - shadowCameraNear: 1, - shadowCameraFar: 1000 - }; - break; - - case 'HemisphereLight': - uniforms = { - direction: new Vector3(), - skyColor: new Color(), - groundColor: new Color() - }; - break; - - case 'RectAreaLight': - uniforms = { - color: new Color(), - position: new Vector3(), - halfWidth: new Vector3(), - halfHeight: new Vector3() - // TODO (abelnation): set RectAreaLight shadow uniforms - }; - break; - - } - - lights[ light.id ] = uniforms; - - return uniforms; - - } - - }; - -} - -function WebGLLights() { - - var cache = new UniformsCache(); - - var state = { - - hash: '', - - ambient: [ 0, 0, 0 ], - directional: [], - directionalShadowMap: [], - directionalShadowMatrix: [], - spot: [], - spotShadowMap: [], - spotShadowMatrix: [], - rectArea: [], - point: [], - pointShadowMap: [], - pointShadowMatrix: [], - hemi: [] - - }; - - var vector3 = new Vector3(); - var matrix4 = new Matrix4(); - var matrix42 = new Matrix4(); - - function setup( lights, shadows, camera ) { - - var r = 0, g = 0, b = 0; - - var directionalLength = 0; - var pointLength = 0; - var spotLength = 0; - var rectAreaLength = 0; - var hemiLength = 0; - - var viewMatrix = camera.matrixWorldInverse; - - for ( var i = 0, l = lights.length; i < l; i ++ ) { - - var light = lights[ i ]; - - var color = light.color; - var intensity = light.intensity; - var distance = light.distance; - - var shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null; - - if ( light.isAmbientLight ) { - - r += color.r * intensity; - g += color.g * intensity; - b += color.b * intensity; - - } else if ( light.isDirectionalLight ) { - - var uniforms = cache.get( light ); - - uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); - - uniforms.shadow = light.castShadow; - - if ( light.castShadow ) { - - var shadow = light.shadow; - - uniforms.shadowBias = shadow.bias; - uniforms.shadowRadius = shadow.radius; - uniforms.shadowMapSize = shadow.mapSize; - - } - - state.directionalShadowMap[ directionalLength ] = shadowMap; - state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; - state.directional[ directionalLength ] = uniforms; - - directionalLength ++; - - } else if ( light.isSpotLight ) { - - var uniforms = cache.get( light ); - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - uniforms.color.copy( color ).multiplyScalar( intensity ); - uniforms.distance = distance; - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - vector3.setFromMatrixPosition( light.target.matrixWorld ); - uniforms.direction.sub( vector3 ); - uniforms.direction.transformDirection( viewMatrix ); - - uniforms.coneCos = Math.cos( light.angle ); - uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); - uniforms.decay = ( light.distance === 0 ) ? 0.0 : light.decay; - - uniforms.shadow = light.castShadow; - - if ( light.castShadow ) { - - var shadow = light.shadow; - - uniforms.shadowBias = shadow.bias; - uniforms.shadowRadius = shadow.radius; - uniforms.shadowMapSize = shadow.mapSize; - - } - - state.spotShadowMap[ spotLength ] = shadowMap; - state.spotShadowMatrix[ spotLength ] = light.shadow.matrix; - state.spot[ spotLength ] = uniforms; - - spotLength ++; - - } else if ( light.isRectAreaLight ) { - - var uniforms = cache.get( light ); - - // (a) intensity controls irradiance of entire light - uniforms.color - .copy( color ) - .multiplyScalar( intensity / ( light.width * light.height ) ); - - // (b) intensity controls the radiance per light area - // uniforms.color.copy( color ).multiplyScalar( intensity ); - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - // extract local rotation of light to derive width/height half vectors - matrix42.identity(); - matrix4.copy( light.matrixWorld ); - matrix4.premultiply( viewMatrix ); - matrix42.extractRotation( matrix4 ); - - uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); - uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); - - uniforms.halfWidth.applyMatrix4( matrix42 ); - uniforms.halfHeight.applyMatrix4( matrix42 ); - - // TODO (abelnation): RectAreaLight distance? - // uniforms.distance = distance; - - state.rectArea[ rectAreaLength ] = uniforms; - - rectAreaLength ++; - - } else if ( light.isPointLight ) { - - var uniforms = cache.get( light ); - - uniforms.position.setFromMatrixPosition( light.matrixWorld ); - uniforms.position.applyMatrix4( viewMatrix ); - - uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); - uniforms.distance = light.distance; - uniforms.decay = ( light.distance === 0 ) ? 0.0 : light.decay; - - uniforms.shadow = light.castShadow; - - if ( light.castShadow ) { - - var shadow = light.shadow; - - uniforms.shadowBias = shadow.bias; - uniforms.shadowRadius = shadow.radius; - uniforms.shadowMapSize = shadow.mapSize; - uniforms.shadowCameraNear = shadow.camera.near; - uniforms.shadowCameraFar = shadow.camera.far; - - } - - state.pointShadowMap[ pointLength ] = shadowMap; - state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; - state.point[ pointLength ] = uniforms; - - pointLength ++; - - } else if ( light.isHemisphereLight ) { - - var uniforms = cache.get( light ); - - uniforms.direction.setFromMatrixPosition( light.matrixWorld ); - uniforms.direction.transformDirection( viewMatrix ); - uniforms.direction.normalize(); - - uniforms.skyColor.copy( light.color ).multiplyScalar( intensity ); - uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity ); - - state.hemi[ hemiLength ] = uniforms; - - hemiLength ++; - - } - - } - - state.ambient[ 0 ] = r; - state.ambient[ 1 ] = g; - state.ambient[ 2 ] = b; - - state.directional.length = directionalLength; - state.spot.length = spotLength; - state.rectArea.length = rectAreaLength; - state.point.length = pointLength; - state.hemi.length = hemiLength; - - // TODO (sam-g-steel) why aren't we using join - state.hash = directionalLength + ',' + pointLength + ',' + spotLength + ',' + rectAreaLength + ',' + hemiLength + ',' + shadows.length; - - } - - return { - setup: setup, - state: state - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLObjects( geometries, infoRender ) { - - var updateList = {}; - - function update( object ) { - - var frame = infoRender.frame; - - var geometry = object.geometry; - var buffergeometry = geometries.get( object, geometry ); - - // Update once per frame - - if ( updateList[ buffergeometry.id ] !== frame ) { - - if ( geometry.isGeometry ) { - - buffergeometry.updateFromObject( object ); - - } - - geometries.update( buffergeometry ); - - updateList[ buffergeometry.id ] = frame; - - } - - return buffergeometry; - - } - - function clear() { - - updateList = {}; - - } - - return { - - update: update, - clear: clear - - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function addLineNumbers( string ) { - - var lines = string.split( '\n' ); - - for ( var i = 0; i < lines.length; i ++ ) { - - lines[ i ] = ( i + 1 ) + ': ' + lines[ i ]; - - } - - return lines.join( '\n' ); - -} - -function WebGLShader( gl, type, string ) { - - var shader = gl.createShader( type ); - - gl.shaderSource( shader, string ); - gl.compileShader( shader ); - - if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === false ) { - - console.error( 'THREE.WebGLShader: Shader couldn\'t compile.' ); - - } - - if ( gl.getShaderInfoLog( shader ) !== '' ) { - - console.warn( 'THREE.WebGLShader: gl.getShaderInfoLog()', type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', gl.getShaderInfoLog( shader ), addLineNumbers( string ) ); - - } - - // --enable-privileged-webgl-extension - // console.log( type, gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); - - return shader; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -var programIdCount = 0; - -function getEncodingComponents( encoding ) { - - switch ( encoding ) { - - case LinearEncoding: - return [ 'Linear', '( value )' ]; - case sRGBEncoding: - return [ 'sRGB', '( value )' ]; - case RGBEEncoding: - return [ 'RGBE', '( value )' ]; - case RGBM7Encoding: - return [ 'RGBM', '( value, 7.0 )' ]; - case RGBM16Encoding: - return [ 'RGBM', '( value, 16.0 )' ]; - case RGBDEncoding: - return [ 'RGBD', '( value, 256.0 )' ]; - case GammaEncoding: - return [ 'Gamma', '( value, float( GAMMA_FACTOR ) )' ]; - default: - throw new Error( 'unsupported encoding: ' + encoding ); - - } - -} - -function getTexelDecodingFunction( functionName, encoding ) { - - var components = getEncodingComponents( encoding ); - return "vec4 " + functionName + "( vec4 value ) { return " + components[ 0 ] + "ToLinear" + components[ 1 ] + "; }"; - -} - -function getTexelEncodingFunction( functionName, encoding ) { - - var components = getEncodingComponents( encoding ); - return "vec4 " + functionName + "( vec4 value ) { return LinearTo" + components[ 0 ] + components[ 1 ] + "; }"; - -} - -function getToneMappingFunction( functionName, toneMapping ) { - - var toneMappingName; - - switch ( toneMapping ) { - - case LinearToneMapping: - toneMappingName = "Linear"; - break; - - case ReinhardToneMapping: - toneMappingName = "Reinhard"; - break; - - case Uncharted2ToneMapping: - toneMappingName = "Uncharted2"; - break; - - case CineonToneMapping: - toneMappingName = "OptimizedCineon"; - break; - - default: - throw new Error( 'unsupported toneMapping: ' + toneMapping ); - - } - - return "vec3 " + functionName + "( vec3 color ) { return " + toneMappingName + "ToneMapping( color ); }"; - -} - -function generateExtensions( extensions, parameters, rendererExtensions ) { - - extensions = extensions || {}; - - var chunks = [ - ( extensions.derivatives || parameters.envMapCubeUV || parameters.bumpMap || parameters.normalMap || parameters.flatShading ) ? '#extension GL_OES_standard_derivatives : enable' : '', - ( extensions.fragDepth || parameters.logarithmicDepthBuffer ) && rendererExtensions.get( 'EXT_frag_depth' ) ? '#extension GL_EXT_frag_depth : enable' : '', - ( extensions.drawBuffers ) && rendererExtensions.get( 'WEBGL_draw_buffers' ) ? '#extension GL_EXT_draw_buffers : require' : '', - ( extensions.shaderTextureLOD || parameters.envMap ) && rendererExtensions.get( 'EXT_shader_texture_lod' ) ? '#extension GL_EXT_shader_texture_lod : enable' : '' - ]; - - return chunks.filter( filterEmptyLine ).join( '\n' ); - -} - -function generateDefines( defines ) { - - var chunks = []; - - for ( var name in defines ) { - - var value = defines[ name ]; - - if ( value === false ) continue; - - chunks.push( '#define ' + name + ' ' + value ); - - } - - return chunks.join( '\n' ); - -} - -function fetchAttributeLocations( gl, program ) { - - var attributes = {}; - - var n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); - - for ( var i = 0; i < n; i ++ ) { - - var info = gl.getActiveAttrib( program, i ); - var name = info.name; - - // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); - - attributes[ name ] = gl.getAttribLocation( program, name ); - - } - - return attributes; - -} - -function filterEmptyLine( string ) { - - return string !== ''; - -} - -function replaceLightNums( string, parameters ) { - - return string - .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) - .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) - .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) - .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) - .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ); - -} - -function parseIncludes( string ) { - - var pattern = /^[ \t]*#include +<([\w\d.]+)>/gm; - - function replace( match, include ) { - - var replace = ShaderChunk[ include ]; - - if ( replace === undefined ) { - - throw new Error( 'Can not resolve #include <' + include + '>' ); - - } - - return parseIncludes( replace ); - - } - - return string.replace( pattern, replace ); - -} - -function unrollLoops( string ) { - - var pattern = /for \( int i \= (\d+)\; i < (\d+)\; i \+\+ \) \{([\s\S]+?)(?=\})\}/g; - - function replace( match, start, end, snippet ) { - - var unroll = ''; - - for ( var i = parseInt( start ); i < parseInt( end ); i ++ ) { - - unroll += snippet.replace( /\[ i \]/g, '[ ' + i + ' ]' ); - - } - - return unroll; - - } - - return string.replace( pattern, replace ); - -} - -function WebGLProgram( renderer, extensions, code, material, shader, parameters ) { - - var gl = renderer.context; - - var defines = material.defines; - - var vertexShader = shader.vertexShader; - var fragmentShader = shader.fragmentShader; - - var shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; - - if ( parameters.shadowMapType === PCFShadowMap ) { - - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; - - } else if ( parameters.shadowMapType === PCFSoftShadowMap ) { - - shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; - - } - - var envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - var envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; - var envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; - - if ( parameters.envMap ) { - - switch ( material.envMap.mapping ) { - - case CubeReflectionMapping: - case CubeRefractionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; - break; - - case CubeUVReflectionMapping: - case CubeUVRefractionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; - break; - - case EquirectangularReflectionMapping: - case EquirectangularRefractionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_EQUIREC'; - break; - - case SphericalReflectionMapping: - envMapTypeDefine = 'ENVMAP_TYPE_SPHERE'; - break; - - } - - switch ( material.envMap.mapping ) { - - case CubeRefractionMapping: - case EquirectangularRefractionMapping: - envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; - break; - - } - - switch ( material.combine ) { - - case MultiplyOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; - break; - - case MixOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; - break; - - case AddOperation: - envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; - break; - - } - - } - - var gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0; - - // console.log( 'building new program ' ); - - // - - var customExtensions = generateExtensions( material.extensions, parameters, extensions ); - - var customDefines = generateDefines( defines ); - - // - - var program = gl.createProgram(); - - var prefixVertex, prefixFragment; - - if ( material.isRawShaderMaterial ) { - - prefixVertex = [ - - customDefines - - ].filter( filterEmptyLine ).join( '\n' ); - - if ( prefixVertex.length > 0 ) { - - prefixVertex += '\n'; - - } - - prefixFragment = [ - - customExtensions, - customDefines - - ].filter( filterEmptyLine ).join( '\n' ); - - if ( prefixFragment.length > 0 ) { - - prefixFragment += '\n'; - - } - - } else { - - prefixVertex = [ - - 'precision ' + parameters.precision + ' float;', - 'precision ' + parameters.precision + ' int;', - - '#define SHADER_NAME ' + shader.name, - - customDefines, - - parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '', - - '#define GAMMA_FACTOR ' + gammaFactorDefine, - - '#define MAX_BONES ' + parameters.maxBones, - ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '', - ( parameters.useFog && parameters.fogExp ) ? '#define FOG_EXP2' : '', - - parameters.map ? '#define USE_MAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '', - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.vertexColors ? '#define USE_COLOR' : '', - - parameters.flatShading ? '#define FLAT_SHADED' : '', - - parameters.skinning ? '#define USE_SKINNING' : '', - parameters.useVertexTexture ? '#define BONE_TEXTURE' : '', - - parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', - parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', - - '#define NUM_CLIPPING_PLANES ' + parameters.numClippingPlanes, - - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - - parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', - - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - parameters.logarithmicDepthBuffer && extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '', - - 'uniform mat4 modelMatrix;', - 'uniform mat4 modelViewMatrix;', - 'uniform mat4 projectionMatrix;', - 'uniform mat4 viewMatrix;', - 'uniform mat3 normalMatrix;', - 'uniform vec3 cameraPosition;', - - 'attribute vec3 position;', - 'attribute vec3 normal;', - 'attribute vec2 uv;', - - '#ifdef USE_COLOR', - - ' attribute vec3 color;', - - '#endif', - - '#ifdef USE_MORPHTARGETS', - - ' attribute vec3 morphTarget0;', - ' attribute vec3 morphTarget1;', - ' attribute vec3 morphTarget2;', - ' attribute vec3 morphTarget3;', - - ' #ifdef USE_MORPHNORMALS', - - ' attribute vec3 morphNormal0;', - ' attribute vec3 morphNormal1;', - ' attribute vec3 morphNormal2;', - ' attribute vec3 morphNormal3;', - - ' #else', - - ' attribute vec3 morphTarget4;', - ' attribute vec3 morphTarget5;', - ' attribute vec3 morphTarget6;', - ' attribute vec3 morphTarget7;', - - ' #endif', - - '#endif', - - '#ifdef USE_SKINNING', - - ' attribute vec4 skinIndex;', - ' attribute vec4 skinWeight;', - - '#endif', - - '\n' - - ].filter( filterEmptyLine ).join( '\n' ); - - prefixFragment = [ - - customExtensions, - - 'precision ' + parameters.precision + ' float;', - 'precision ' + parameters.precision + ' int;', - - '#define SHADER_NAME ' + shader.name, - - customDefines, - - parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest : '', - - '#define GAMMA_FACTOR ' + gammaFactorDefine, - - ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '', - ( parameters.useFog && parameters.fogExp ) ? '#define FOG_EXP2' : '', - - parameters.map ? '#define USE_MAP' : '', - parameters.envMap ? '#define USE_ENVMAP' : '', - parameters.envMap ? '#define ' + envMapTypeDefine : '', - parameters.envMap ? '#define ' + envMapModeDefine : '', - parameters.envMap ? '#define ' + envMapBlendingDefine : '', - parameters.lightMap ? '#define USE_LIGHTMAP' : '', - parameters.aoMap ? '#define USE_AOMAP' : '', - parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', - parameters.bumpMap ? '#define USE_BUMPMAP' : '', - parameters.normalMap ? '#define USE_NORMALMAP' : '', - parameters.specularMap ? '#define USE_SPECULARMAP' : '', - parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', - parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', - parameters.alphaMap ? '#define USE_ALPHAMAP' : '', - parameters.vertexColors ? '#define USE_COLOR' : '', - - parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', - - parameters.flatShading ? '#define FLAT_SHADED' : '', - - parameters.doubleSided ? '#define DOUBLE_SIDED' : '', - parameters.flipSided ? '#define FLIP_SIDED' : '', - - '#define NUM_CLIPPING_PLANES ' + parameters.numClippingPlanes, - '#define UNION_CLIPPING_PLANES ' + ( parameters.numClippingPlanes - parameters.numClipIntersection ), - - parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', - parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', - - parameters.premultipliedAlpha ? "#define PREMULTIPLIED_ALPHA" : '', - - parameters.physicallyCorrectLights ? "#define PHYSICALLY_CORRECT_LIGHTS" : '', - - parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', - parameters.logarithmicDepthBuffer && extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '', - - parameters.envMap && extensions.get( 'EXT_shader_texture_lod' ) ? '#define TEXTURE_LOD_EXT' : '', - - 'uniform mat4 viewMatrix;', - 'uniform vec3 cameraPosition;', - - ( parameters.toneMapping !== NoToneMapping ) ? "#define TONE_MAPPING" : '', - ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below - ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( "toneMapping", parameters.toneMapping ) : '', - - parameters.dithering ? '#define DITHERING' : '', - - ( parameters.outputEncoding || parameters.mapEncoding || parameters.envMapEncoding || parameters.emissiveMapEncoding ) ? ShaderChunk[ 'encodings_pars_fragment' ] : '', // this code is required here because it is used by the various encoding/decoding function defined below - parameters.mapEncoding ? getTexelDecodingFunction( 'mapTexelToLinear', parameters.mapEncoding ) : '', - parameters.envMapEncoding ? getTexelDecodingFunction( 'envMapTexelToLinear', parameters.envMapEncoding ) : '', - parameters.emissiveMapEncoding ? getTexelDecodingFunction( 'emissiveMapTexelToLinear', parameters.emissiveMapEncoding ) : '', - parameters.outputEncoding ? getTexelEncodingFunction( "linearToOutputTexel", parameters.outputEncoding ) : '', - - parameters.depthPacking ? "#define DEPTH_PACKING " + material.depthPacking : '', - - '\n' - - ].filter( filterEmptyLine ).join( '\n' ); - - } - - vertexShader = parseIncludes( vertexShader ); - vertexShader = replaceLightNums( vertexShader, parameters ); - - fragmentShader = parseIncludes( fragmentShader ); - fragmentShader = replaceLightNums( fragmentShader, parameters ); - - if ( ! material.isShaderMaterial ) { - - vertexShader = unrollLoops( vertexShader ); - fragmentShader = unrollLoops( fragmentShader ); - - } - - var vertexGlsl = prefixVertex + vertexShader; - var fragmentGlsl = prefixFragment + fragmentShader; - - // console.log( '*VERTEX*', vertexGlsl ); - // console.log( '*FRAGMENT*', fragmentGlsl ); - - var glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); - var glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); - - gl.attachShader( program, glVertexShader ); - gl.attachShader( program, glFragmentShader ); - - // Force a particular attribute to index 0. - - if ( material.index0AttributeName !== undefined ) { - - gl.bindAttribLocation( program, 0, material.index0AttributeName ); - - } else if ( parameters.morphTargets === true ) { - - // programs with morphTargets displace position out of attribute 0 - gl.bindAttribLocation( program, 0, 'position' ); - - } - - gl.linkProgram( program ); - - var programLog = gl.getProgramInfoLog( program ); - var vertexLog = gl.getShaderInfoLog( glVertexShader ); - var fragmentLog = gl.getShaderInfoLog( glFragmentShader ); - - var runnable = true; - var haveDiagnostics = true; - - // console.log( '**VERTEX**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glVertexShader ) ); - // console.log( '**FRAGMENT**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glFragmentShader ) ); - - if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { - - runnable = false; - - console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'gl.getProgramInfoLog', programLog, vertexLog, fragmentLog ); - - } else if ( programLog !== '' ) { - - console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog ); - - } else if ( vertexLog === '' || fragmentLog === '' ) { - - haveDiagnostics = false; - - } - - if ( haveDiagnostics ) { - - this.diagnostics = { - - runnable: runnable, - material: material, - - programLog: programLog, - - vertexShader: { - - log: vertexLog, - prefix: prefixVertex - - }, - - fragmentShader: { - - log: fragmentLog, - prefix: prefixFragment - - } - - }; - - } - - // clean up - - gl.deleteShader( glVertexShader ); - gl.deleteShader( glFragmentShader ); - - // set up caching for uniform locations - - var cachedUniforms; - - this.getUniforms = function () { - - if ( cachedUniforms === undefined ) { - - cachedUniforms = new WebGLUniforms( gl, program, renderer ); - - } - - return cachedUniforms; - - }; - - // set up caching for attribute locations - - var cachedAttributes; - - this.getAttributes = function () { - - if ( cachedAttributes === undefined ) { - - cachedAttributes = fetchAttributeLocations( gl, program ); - - } - - return cachedAttributes; - - }; - - // free resource - - this.destroy = function () { - - gl.deleteProgram( program ); - this.program = undefined; - - }; - - // DEPRECATED - - Object.defineProperties( this, { - - uniforms: { - get: function () { - - console.warn( 'THREE.WebGLProgram: .uniforms is now .getUniforms().' ); - return this.getUniforms(); - - } - }, - - attributes: { - get: function () { - - console.warn( 'THREE.WebGLProgram: .attributes is now .getAttributes().' ); - return this.getAttributes(); - - } - } - - } ); - - - // - - this.id = programIdCount ++; - this.code = code; - this.usedTimes = 1; - this.program = program; - this.vertexShader = glVertexShader; - this.fragmentShader = glFragmentShader; - - return this; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLPrograms( renderer, extensions, capabilities ) { - - var programs = []; - - var shaderIDs = { - MeshDepthMaterial: 'depth', - MeshDistanceMaterial: 'distanceRGBA', - MeshNormalMaterial: 'normal', - MeshBasicMaterial: 'basic', - MeshLambertMaterial: 'lambert', - MeshPhongMaterial: 'phong', - MeshToonMaterial: 'phong', - MeshStandardMaterial: 'physical', - MeshPhysicalMaterial: 'physical', - LineBasicMaterial: 'basic', - LineDashedMaterial: 'dashed', - PointsMaterial: 'points', - ShadowMaterial: 'shadow' - }; - - var parameterNames = [ - "precision", "supportsVertexTextures", "map", "mapEncoding", "envMap", "envMapMode", "envMapEncoding", - "lightMap", "aoMap", "emissiveMap", "emissiveMapEncoding", "bumpMap", "normalMap", "displacementMap", "specularMap", - "roughnessMap", "metalnessMap", "gradientMap", - "alphaMap", "combine", "vertexColors", "fog", "useFog", "fogExp", - "flatShading", "sizeAttenuation", "logarithmicDepthBuffer", "skinning", - "maxBones", "useVertexTexture", "morphTargets", "morphNormals", - "maxMorphTargets", "maxMorphNormals", "premultipliedAlpha", - "numDirLights", "numPointLights", "numSpotLights", "numHemiLights", "numRectAreaLights", - "shadowMapEnabled", "shadowMapType", "toneMapping", 'physicallyCorrectLights', - "alphaTest", "doubleSided", "flipSided", "numClippingPlanes", "numClipIntersection", "depthPacking", "dithering" - ]; - - - function allocateBones( object ) { - - var skeleton = object.skeleton; - var bones = skeleton.bones; - - if ( capabilities.floatVertexTextures ) { - - return 1024; - - } else { - - // default for when object is not specified - // ( for example when prebuilding shader to be used with multiple objects ) - // - // - leave some extra space for other uniforms - // - limit here is ANGLE's 254 max uniform vectors - // (up to 54 should be safe) - - var nVertexUniforms = capabilities.maxVertexUniforms; - var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 ); - - var maxBones = Math.min( nVertexMatrices, bones.length ); - - if ( maxBones < bones.length ) { - - console.warn( 'THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.' ); - return 0; - - } - - return maxBones; - - } - - } - - function getTextureEncodingFromMap( map, gammaOverrideLinear ) { - - var encoding; - - if ( ! map ) { - - encoding = LinearEncoding; - - } else if ( map.isTexture ) { - - encoding = map.encoding; - - } else if ( map.isWebGLRenderTarget ) { - - console.warn( "THREE.WebGLPrograms.getTextureEncodingFromMap: don't use render targets as textures. Use their .texture property instead." ); - encoding = map.texture.encoding; - - } - - // add backwards compatibility for WebGLRenderer.gammaInput/gammaOutput parameter, should probably be removed at some point. - if ( encoding === LinearEncoding && gammaOverrideLinear ) { - - encoding = GammaEncoding; - - } - - return encoding; - - } - - this.getParameters = function ( material, lights, shadows, fog, nClipPlanes, nClipIntersection, object ) { - - var shaderID = shaderIDs[ material.type ]; - - // heuristics to create shader parameters according to lights in the scene - // (not to blow over maxLights budget) - - var maxBones = object.isSkinnedMesh ? allocateBones( object ) : 0; - var precision = capabilities.precision; - - if ( material.precision !== null ) { - - precision = capabilities.getMaxPrecision( material.precision ); - - if ( precision !== material.precision ) { - - console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); - - } - - } - - var currentRenderTarget = renderer.getRenderTarget(); - - var parameters = { - - shaderID: shaderID, - - precision: precision, - supportsVertexTextures: capabilities.vertexTextures, - outputEncoding: getTextureEncodingFromMap( ( ! currentRenderTarget ) ? null : currentRenderTarget.texture, renderer.gammaOutput ), - map: !! material.map, - mapEncoding: getTextureEncodingFromMap( material.map, renderer.gammaInput ), - envMap: !! material.envMap, - envMapMode: material.envMap && material.envMap.mapping, - envMapEncoding: getTextureEncodingFromMap( material.envMap, renderer.gammaInput ), - envMapCubeUV: ( !! material.envMap ) && ( ( material.envMap.mapping === CubeUVReflectionMapping ) || ( material.envMap.mapping === CubeUVRefractionMapping ) ), - lightMap: !! material.lightMap, - aoMap: !! material.aoMap, - emissiveMap: !! material.emissiveMap, - emissiveMapEncoding: getTextureEncodingFromMap( material.emissiveMap, renderer.gammaInput ), - bumpMap: !! material.bumpMap, - normalMap: !! material.normalMap, - displacementMap: !! material.displacementMap, - roughnessMap: !! material.roughnessMap, - metalnessMap: !! material.metalnessMap, - specularMap: !! material.specularMap, - alphaMap: !! material.alphaMap, - - gradientMap: !! material.gradientMap, - - combine: material.combine, - - vertexColors: material.vertexColors, - - fog: !! fog, - useFog: material.fog, - fogExp: ( fog && fog.isFogExp2 ), - - flatShading: material.flatShading, - - sizeAttenuation: material.sizeAttenuation, - logarithmicDepthBuffer: capabilities.logarithmicDepthBuffer, - - skinning: material.skinning && maxBones > 0, - maxBones: maxBones, - useVertexTexture: capabilities.floatVertexTextures, - - morphTargets: material.morphTargets, - morphNormals: material.morphNormals, - maxMorphTargets: renderer.maxMorphTargets, - maxMorphNormals: renderer.maxMorphNormals, - - numDirLights: lights.directional.length, - numPointLights: lights.point.length, - numSpotLights: lights.spot.length, - numRectAreaLights: lights.rectArea.length, - numHemiLights: lights.hemi.length, - - numClippingPlanes: nClipPlanes, - numClipIntersection: nClipIntersection, - - dithering: material.dithering, - - shadowMapEnabled: renderer.shadowMap.enabled && object.receiveShadow && shadows.length > 0, - shadowMapType: renderer.shadowMap.type, - - toneMapping: renderer.toneMapping, - physicallyCorrectLights: renderer.physicallyCorrectLights, - - premultipliedAlpha: material.premultipliedAlpha, - - alphaTest: material.alphaTest, - doubleSided: material.side === DoubleSide, - flipSided: material.side === BackSide, - - depthPacking: ( material.depthPacking !== undefined ) ? material.depthPacking : false - - }; - - return parameters; - - }; - - this.getProgramCode = function ( material, parameters ) { - - var array = []; - - if ( parameters.shaderID ) { - - array.push( parameters.shaderID ); - - } else { - - array.push( material.fragmentShader ); - array.push( material.vertexShader ); - - } - - if ( material.defines !== undefined ) { - - for ( var name in material.defines ) { - - array.push( name ); - array.push( material.defines[ name ] ); - - } - - } - - for ( var i = 0; i < parameterNames.length; i ++ ) { - - array.push( parameters[ parameterNames[ i ] ] ); - - } - - array.push( material.onBeforeCompile.toString() ); - - array.push( renderer.gammaOutput ); - - return array.join(); - - }; - - this.acquireProgram = function ( material, shader, parameters, code ) { - - var program; - - // Check if code has been already compiled - for ( var p = 0, pl = programs.length; p < pl; p ++ ) { - - var programInfo = programs[ p ]; - - if ( programInfo.code === code ) { - - program = programInfo; - ++ program.usedTimes; - - break; - - } - - } - - if ( program === undefined ) { - - program = new WebGLProgram( renderer, extensions, code, material, shader, parameters ); - programs.push( program ); - - } - - return program; - - }; - - this.releaseProgram = function ( program ) { - - if ( -- program.usedTimes === 0 ) { - - // Remove from unordered set - var i = programs.indexOf( program ); - programs[ i ] = programs[ programs.length - 1 ]; - programs.pop(); - - // Free WebGL resources - program.destroy(); - - } - - }; - - // Exposed for resource monitoring & error feedback via renderer.info: - this.programs = programs; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, infoMemory ) { - - var _isWebGL2 = ( typeof WebGL2RenderingContext !== 'undefined' && _gl instanceof window.WebGL2RenderingContext ); - - // - - function clampToMaxSize( image, maxSize ) { - - if ( image.width > maxSize || image.height > maxSize ) { - - // Warning: Scaling through the canvas will only work with images that use - // premultiplied alpha. - - var scale = maxSize / Math.max( image.width, image.height ); - - var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); - canvas.width = Math.floor( image.width * scale ); - canvas.height = Math.floor( image.height * scale ); - - var context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height ); - - console.warn( 'THREE.WebGLRenderer: image is too big (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height, image ); - - return canvas; - - } - - return image; - - } - - function isPowerOfTwo( image ) { - - return _Math.isPowerOfTwo( image.width ) && _Math.isPowerOfTwo( image.height ); - - } - - function makePowerOfTwo( image ) { - - if ( image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof ImageBitmap ) { - - var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); - canvas.width = _Math.floorPowerOfTwo( image.width ); - canvas.height = _Math.floorPowerOfTwo( image.height ); - - var context = canvas.getContext( '2d' ); - context.drawImage( image, 0, 0, canvas.width, canvas.height ); - - console.warn( 'THREE.WebGLRenderer: image is not power of two (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height, image ); - - return canvas; - - } - - return image; - - } - - function textureNeedsPowerOfTwo( texture ) { - - return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) || - ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ); - - } - - function textureNeedsGenerateMipmaps( texture, isPowerOfTwo ) { - - return texture.generateMipmaps && isPowerOfTwo && - texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter; - - } - - // Fallback filters for non-power-of-2 textures - - function filterFallback( f ) { - - if ( f === NearestFilter || f === NearestMipMapNearestFilter || f === NearestMipMapLinearFilter ) { - - return _gl.NEAREST; - - } - - return _gl.LINEAR; - - } - - // - - function onTextureDispose( event ) { - - var texture = event.target; - - texture.removeEventListener( 'dispose', onTextureDispose ); - - deallocateTexture( texture ); - - infoMemory.textures --; - - - } - - function onRenderTargetDispose( event ) { - - var renderTarget = event.target; - - renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); - - deallocateRenderTarget( renderTarget ); - - infoMemory.textures --; - - } - - // - - function deallocateTexture( texture ) { - - var textureProperties = properties.get( texture ); - - if ( texture.image && textureProperties.__image__webglTextureCube ) { - - // cube texture - - _gl.deleteTexture( textureProperties.__image__webglTextureCube ); - - } else { - - // 2D texture - - if ( textureProperties.__webglInit === undefined ) return; - - _gl.deleteTexture( textureProperties.__webglTexture ); - - } - - // remove all webgl properties - properties.remove( texture ); - - } - - function deallocateRenderTarget( renderTarget ) { - - var renderTargetProperties = properties.get( renderTarget ); - var textureProperties = properties.get( renderTarget.texture ); - - if ( ! renderTarget ) return; - - if ( textureProperties.__webglTexture !== undefined ) { - - _gl.deleteTexture( textureProperties.__webglTexture ); - - } - - if ( renderTarget.depthTexture ) { - - renderTarget.depthTexture.dispose(); - - } - - if ( renderTarget.isWebGLRenderTargetCube ) { - - for ( var i = 0; i < 6; i ++ ) { - - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); - - } - - } else { - - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); - - } - - properties.remove( renderTarget.texture ); - properties.remove( renderTarget ); - - } - - // - - - - function setTexture2D( texture, slot ) { - - var textureProperties = properties.get( texture ); - - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - - var image = texture.image; - - if ( image === undefined ) { - - console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined', texture ); - - } else if ( image.complete === false ) { - - console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete', texture ); - - } else { - - uploadTexture( textureProperties, texture, slot ); - return; - - } - - } - - state.activeTexture( _gl.TEXTURE0 + slot ); - state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); - - } - - function setTextureCube( texture, slot ) { - - var textureProperties = properties.get( texture ); - - if ( texture.image.length === 6 ) { - - if ( texture.version > 0 && textureProperties.__version !== texture.version ) { - - if ( ! textureProperties.__image__webglTextureCube ) { - - texture.addEventListener( 'dispose', onTextureDispose ); - - textureProperties.__image__webglTextureCube = _gl.createTexture(); - - infoMemory.textures ++; - - } - - state.activeTexture( _gl.TEXTURE0 + slot ); - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube ); - - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - - var isCompressed = ( texture && texture.isCompressedTexture ); - var isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); - - var cubeImage = []; - - for ( var i = 0; i < 6; i ++ ) { - - if ( ! isCompressed && ! isDataTexture ) { - - cubeImage[ i ] = clampToMaxSize( texture.image[ i ], capabilities.maxCubemapSize ); - - } else { - - cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; - - } - - } - - var image = cubeImage[ 0 ], - isPowerOfTwoImage = isPowerOfTwo( image ), - glFormat = utils.convert( texture.format ), - glType = utils.convert( texture.type ); - - setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isPowerOfTwoImage ); - - for ( var i = 0; i < 6; i ++ ) { - - if ( ! isCompressed ) { - - if ( isDataTexture ) { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] ); - - } - - } else { - - var mipmap, mipmaps = cubeImage[ i ].mipmaps; - - for ( var j = 0, jl = mipmaps.length; j < jl; j ++ ) { - - mipmap = mipmaps[ j ]; - - if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) { - - if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) { - - state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - - } else { - - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - } - - } - - if ( textureNeedsGenerateMipmaps( texture, isPowerOfTwoImage ) ) { - - _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); - - } - - textureProperties.__version = texture.version; - - if ( texture.onUpdate ) texture.onUpdate( texture ); - - } else { - - state.activeTexture( _gl.TEXTURE0 + slot ); - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube ); - - } - - } - - } - - function setTextureCubeDynamic( texture, slot ) { - - state.activeTexture( _gl.TEXTURE0 + slot ); - state.bindTexture( _gl.TEXTURE_CUBE_MAP, properties.get( texture ).__webglTexture ); - - } - - function setTextureParameters( textureType, texture, isPowerOfTwoImage ) { - - var extension; - - if ( isPowerOfTwoImage ) { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, utils.convert( texture.wrapS ) ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, utils.convert( texture.wrapT ) ); - - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, utils.convert( texture.magFilter ) ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, utils.convert( texture.minFilter ) ); - - } else { - - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); - _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); - - if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) { - - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.', texture ); - - } - - _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); - _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); - - if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) { - - console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.', texture ); - - } - - } - - extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - - if ( extension ) { - - if ( texture.type === FloatType && extensions.get( 'OES_texture_float_linear' ) === null ) return; - if ( texture.type === HalfFloatType && extensions.get( 'OES_texture_half_float_linear' ) === null ) return; - - if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { - - _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); - properties.get( texture ).__currentAnisotropy = texture.anisotropy; - - } - - } - - } - - function uploadTexture( textureProperties, texture, slot ) { - - if ( textureProperties.__webglInit === undefined ) { - - textureProperties.__webglInit = true; - - texture.addEventListener( 'dispose', onTextureDispose ); - - textureProperties.__webglTexture = _gl.createTexture(); - - infoMemory.textures ++; - - } - - state.activeTexture( _gl.TEXTURE0 + slot ); - state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); - - _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); - _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); - _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); - - var image = clampToMaxSize( texture.image, capabilities.maxTextureSize ); - - if ( textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( image ) === false ) { - - image = makePowerOfTwo( image ); - - } - - var isPowerOfTwoImage = isPowerOfTwo( image ), - glFormat = utils.convert( texture.format ), - glType = utils.convert( texture.type ); - - setTextureParameters( _gl.TEXTURE_2D, texture, isPowerOfTwoImage ); - - var mipmap, mipmaps = texture.mipmaps; - - if ( texture.isDepthTexture ) { - - // populate depth texture with dummy data - - var internalFormat = _gl.DEPTH_COMPONENT; - - if ( texture.type === FloatType ) { - - if ( ! _isWebGL2 ) throw new Error( 'Float Depth Texture only supported in WebGL2.0' ); - internalFormat = _gl.DEPTH_COMPONENT32F; - - } else if ( _isWebGL2 ) { - - // WebGL 2.0 requires signed internalformat for glTexImage2D - internalFormat = _gl.DEPTH_COMPONENT16; - - } - - if ( texture.format === DepthFormat && internalFormat === _gl.DEPTH_COMPONENT ) { - - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) { - - console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' ); - - texture.type = UnsignedShortType; - glType = utils.convert( texture.type ); - - } - - } - - // Depth stencil textures need the DEPTH_STENCIL internal format - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.format === DepthStencilFormat ) { - - internalFormat = _gl.DEPTH_STENCIL; - - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL. - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedInt248Type ) { - - console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' ); - - texture.type = UnsignedInt248Type; - glType = utils.convert( texture.type ); - - } - - } - - state.texImage2D( _gl.TEXTURE_2D, 0, internalFormat, image.width, image.height, 0, glFormat, glType, null ); - - } else if ( texture.isDataTexture ) { - - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels - - if ( mipmaps.length > 0 && isPowerOfTwoImage ) { - - for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - texture.generateMipmaps = false; - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data ); - - } - - } else if ( texture.isCompressedTexture ) { - - for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - - if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) { - - if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) { - - state.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); - - } else { - - console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); - - } - - } else { - - state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); - - } - - } - - } else { - - // regular Texture (image, video, canvas) - - // use manually created mipmaps if available - // if there are no manual mipmaps - // set 0 level mipmap and then use GL to generate other mipmap levels - - if ( mipmaps.length > 0 && isPowerOfTwoImage ) { - - for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { - - mipmap = mipmaps[ i ]; - state.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap ); - - } - - texture.generateMipmaps = false; - - } else { - - state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, image ); - - } - - } - - if ( textureNeedsGenerateMipmaps( texture, isPowerOfTwoImage ) ) _gl.generateMipmap( _gl.TEXTURE_2D ); - - textureProperties.__version = texture.version; - - if ( texture.onUpdate ) texture.onUpdate( texture ); - - } - - // Render targets - - // Setup storage for target texture and bind it to correct framebuffer - function setupFrameBufferTexture( framebuffer, renderTarget, attachment, textureTarget ) { - - var glFormat = utils.convert( renderTarget.texture.format ); - var glType = utils.convert( renderTarget.texture.type ); - state.texImage2D( textureTarget, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); - _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( renderTarget.texture ).__webglTexture, 0 ); - _gl.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - // Setup storage for internal depth/stencil buffers and bind to correct framebuffer - function setupRenderBufferStorage( renderbuffer, renderTarget ) { - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); - - if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); - - } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { - - _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); - _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); - - } else { - - // FIXME: We don't support !depth !stencil - _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height ); - - } - - _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); - - } - - // Setup resources for a Depth Texture for a FBO (needs an extension) - function setupDepthTexture( framebuffer, renderTarget ) { - - var isCube = ( renderTarget && renderTarget.isWebGLRenderTargetCube ); - if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' ); - - _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { - - throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' ); - - } - - // upload an empty depth texture with framebuffer size - if ( ! properties.get( renderTarget.depthTexture ).__webglTexture || - renderTarget.depthTexture.image.width !== renderTarget.width || - renderTarget.depthTexture.image.height !== renderTarget.height ) { - - renderTarget.depthTexture.image.width = renderTarget.width; - renderTarget.depthTexture.image.height = renderTarget.height; - renderTarget.depthTexture.needsUpdate = true; - - } - - setTexture2D( renderTarget.depthTexture, 0 ); - - var webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture; - - if ( renderTarget.depthTexture.format === DepthFormat ) { - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - - } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { - - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); - - } else { - - throw new Error( 'Unknown depthTexture format' ); - - } - - } - - // Setup GL resources for a non-texture depth buffer - function setupDepthRenderbuffer( renderTarget ) { - - var renderTargetProperties = properties.get( renderTarget ); - - var isCube = ( renderTarget.isWebGLRenderTargetCube === true ); - - if ( renderTarget.depthTexture ) { - - if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); - - setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); - - } else { - - if ( isCube ) { - - renderTargetProperties.__webglDepthbuffer = []; - - for ( var i = 0; i < 6; i ++ ) { - - _gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); - renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget ); - - } - - } else { - - _gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); - renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); - setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget ); - - } - - } - - _gl.bindFramebuffer( _gl.FRAMEBUFFER, null ); - - } - - // Set up GL resources for the render target - function setupRenderTarget( renderTarget ) { - - var renderTargetProperties = properties.get( renderTarget ); - var textureProperties = properties.get( renderTarget.texture ); - - renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); - - textureProperties.__webglTexture = _gl.createTexture(); - - infoMemory.textures ++; - - var isCube = ( renderTarget.isWebGLRenderTargetCube === true ); - var isTargetPowerOfTwo = isPowerOfTwo( renderTarget ); - - // Setup framebuffer - - if ( isCube ) { - - renderTargetProperties.__webglFramebuffer = []; - - for ( var i = 0; i < 6; i ++ ) { - - renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); - - } - - } else { - - renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); - - } - - // Setup color buffer - - if ( isCube ) { - - state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget.texture, isTargetPowerOfTwo ); - - for ( var i = 0; i < 6; i ++ ) { - - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i ); - - } - - if ( textureNeedsGenerateMipmaps( renderTarget.texture, isTargetPowerOfTwo ) ) _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); - state.bindTexture( _gl.TEXTURE_CUBE_MAP, null ); - - } else { - - state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); - setTextureParameters( _gl.TEXTURE_2D, renderTarget.texture, isTargetPowerOfTwo ); - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D ); - - if ( textureNeedsGenerateMipmaps( renderTarget.texture, isTargetPowerOfTwo ) ) _gl.generateMipmap( _gl.TEXTURE_2D ); - state.bindTexture( _gl.TEXTURE_2D, null ); - - } - - // Setup depth and stencil buffers - - if ( renderTarget.depthBuffer ) { - - setupDepthRenderbuffer( renderTarget ); - - } - - } - - function updateRenderTargetMipmap( renderTarget ) { - - var texture = renderTarget.texture; - var isTargetPowerOfTwo = isPowerOfTwo( renderTarget ); - - if ( textureNeedsGenerateMipmaps( texture, isTargetPowerOfTwo ) ) { - - var target = renderTarget.isWebGLRenderTargetCube ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D; - var webglTexture = properties.get( texture ).__webglTexture; - - state.bindTexture( target, webglTexture ); - _gl.generateMipmap( target ); - state.bindTexture( target, null ); - - } - - } - - this.setTexture2D = setTexture2D; - this.setTextureCube = setTextureCube; - this.setTextureCubeDynamic = setTextureCubeDynamic; - this.setupRenderTarget = setupRenderTarget; - this.updateRenderTargetMipmap = updateRenderTargetMipmap; - -} - -/** - * @author fordacious / fordacious.github.io - */ - -function WebGLProperties() { - - var properties = {}; - - function get( object ) { - - var uuid = object.uuid; - var map = properties[ uuid ]; - - if ( map === undefined ) { - - map = {}; - properties[ uuid ] = map; - - } - - return map; - - } - - function remove( object ) { - - delete properties[ object.uuid ]; - - } - - function clear() { - - properties = {}; - - } - - return { - get: get, - remove: remove, - clear: clear - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLState( gl, extensions, utils ) { - - function ColorBuffer() { - - var locked = false; - - var color = new Vector4(); - var currentColorMask = null; - var currentColorClear = new Vector4( 0, 0, 0, 0 ); - - return { - - setMask: function ( colorMask ) { - - if ( currentColorMask !== colorMask && ! locked ) { - - gl.colorMask( colorMask, colorMask, colorMask, colorMask ); - currentColorMask = colorMask; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( r, g, b, a, premultipliedAlpha ) { - - if ( premultipliedAlpha === true ) { - - r *= a; g *= a; b *= a; - - } - - color.set( r, g, b, a ); - - if ( currentColorClear.equals( color ) === false ) { - - gl.clearColor( r, g, b, a ); - currentColorClear.copy( color ); - - } - - }, - - reset: function () { - - locked = false; - - currentColorMask = null; - currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state - - } - - }; - - } - - function DepthBuffer() { - - var locked = false; - - var currentDepthMask = null; - var currentDepthFunc = null; - var currentDepthClear = null; - - return { - - setTest: function ( depthTest ) { - - if ( depthTest ) { - - enable( gl.DEPTH_TEST ); - - } else { - - disable( gl.DEPTH_TEST ); - - } - - }, - - setMask: function ( depthMask ) { - - if ( currentDepthMask !== depthMask && ! locked ) { - - gl.depthMask( depthMask ); - currentDepthMask = depthMask; - - } - - }, - - setFunc: function ( depthFunc ) { - - if ( currentDepthFunc !== depthFunc ) { - - if ( depthFunc ) { - - switch ( depthFunc ) { - - case NeverDepth: - - gl.depthFunc( gl.NEVER ); - break; - - case AlwaysDepth: - - gl.depthFunc( gl.ALWAYS ); - break; - - case LessDepth: - - gl.depthFunc( gl.LESS ); - break; - - case LessEqualDepth: - - gl.depthFunc( gl.LEQUAL ); - break; - - case EqualDepth: - - gl.depthFunc( gl.EQUAL ); - break; - - case GreaterEqualDepth: - - gl.depthFunc( gl.GEQUAL ); - break; - - case GreaterDepth: - - gl.depthFunc( gl.GREATER ); - break; - - case NotEqualDepth: - - gl.depthFunc( gl.NOTEQUAL ); - break; - - default: - - gl.depthFunc( gl.LEQUAL ); - - } - - } else { - - gl.depthFunc( gl.LEQUAL ); - - } - - currentDepthFunc = depthFunc; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( depth ) { - - if ( currentDepthClear !== depth ) { - - gl.clearDepth( depth ); - currentDepthClear = depth; - - } - - }, - - reset: function () { - - locked = false; - - currentDepthMask = null; - currentDepthFunc = null; - currentDepthClear = null; - - } - - }; - - } - - function StencilBuffer() { - - var locked = false; - - var currentStencilMask = null; - var currentStencilFunc = null; - var currentStencilRef = null; - var currentStencilFuncMask = null; - var currentStencilFail = null; - var currentStencilZFail = null; - var currentStencilZPass = null; - var currentStencilClear = null; - - return { - - setTest: function ( stencilTest ) { - - if ( stencilTest ) { - - enable( gl.STENCIL_TEST ); - - } else { - - disable( gl.STENCIL_TEST ); - - } - - }, - - setMask: function ( stencilMask ) { - - if ( currentStencilMask !== stencilMask && ! locked ) { - - gl.stencilMask( stencilMask ); - currentStencilMask = stencilMask; - - } - - }, - - setFunc: function ( stencilFunc, stencilRef, stencilMask ) { - - if ( currentStencilFunc !== stencilFunc || - currentStencilRef !== stencilRef || - currentStencilFuncMask !== stencilMask ) { - - gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); - - currentStencilFunc = stencilFunc; - currentStencilRef = stencilRef; - currentStencilFuncMask = stencilMask; - - } - - }, - - setOp: function ( stencilFail, stencilZFail, stencilZPass ) { - - if ( currentStencilFail !== stencilFail || - currentStencilZFail !== stencilZFail || - currentStencilZPass !== stencilZPass ) { - - gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); - - currentStencilFail = stencilFail; - currentStencilZFail = stencilZFail; - currentStencilZPass = stencilZPass; - - } - - }, - - setLocked: function ( lock ) { - - locked = lock; - - }, - - setClear: function ( stencil ) { - - if ( currentStencilClear !== stencil ) { - - gl.clearStencil( stencil ); - currentStencilClear = stencil; - - } - - }, - - reset: function () { - - locked = false; - - currentStencilMask = null; - currentStencilFunc = null; - currentStencilRef = null; - currentStencilFuncMask = null; - currentStencilFail = null; - currentStencilZFail = null; - currentStencilZPass = null; - currentStencilClear = null; - - } - - }; - - } - - // - - var colorBuffer = new ColorBuffer(); - var depthBuffer = new DepthBuffer(); - var stencilBuffer = new StencilBuffer(); - - var maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - var newAttributes = new Uint8Array( maxVertexAttributes ); - var enabledAttributes = new Uint8Array( maxVertexAttributes ); - var attributeDivisors = new Uint8Array( maxVertexAttributes ); - - var capabilities = {}; - - var compressedTextureFormats = null; - - var currentProgram = null; - - var currentBlending = null; - var currentBlendEquation = null; - var currentBlendSrc = null; - var currentBlendDst = null; - var currentBlendEquationAlpha = null; - var currentBlendSrcAlpha = null; - var currentBlendDstAlpha = null; - var currentPremultipledAlpha = false; - - var currentFlipSided = null; - var currentCullFace = null; - - var currentLineWidth = null; - - var currentPolygonOffsetFactor = null; - var currentPolygonOffsetUnits = null; - - var maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); - - var version = parseFloat( /^WebGL\ ([0-9])/.exec( gl.getParameter( gl.VERSION ) )[ 1 ] ); - var lineWidthAvailable = parseFloat( version ) >= 1.0; - - var currentTextureSlot = null; - var currentBoundTextures = {}; - - var currentScissor = new Vector4(); - var currentViewport = new Vector4(); - - function createTexture( type, target, count ) { - - var data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. - var texture = gl.createTexture(); - - gl.bindTexture( type, texture ); - gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); - gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - - for ( var i = 0; i < count; i ++ ) { - - gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); - - } - - return texture; - - } - - var emptyTextures = {}; - emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); - emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); - - // init - - colorBuffer.setClear( 0, 0, 0, 1 ); - depthBuffer.setClear( 1 ); - stencilBuffer.setClear( 0 ); - - enable( gl.DEPTH_TEST ); - depthBuffer.setFunc( LessEqualDepth ); - - setFlipSided( false ); - setCullFace( CullFaceBack ); - enable( gl.CULL_FACE ); - - enable( gl.BLEND ); - setBlending( NormalBlending ); - - // - - function initAttributes() { - - for ( var i = 0, l = newAttributes.length; i < l; i ++ ) { - - newAttributes[ i ] = 0; - - } - - } - - function enableAttribute( attribute ) { - - newAttributes[ attribute ] = 1; - - if ( enabledAttributes[ attribute ] === 0 ) { - - gl.enableVertexAttribArray( attribute ); - enabledAttributes[ attribute ] = 1; - - } - - if ( attributeDivisors[ attribute ] !== 0 ) { - - var extension = extensions.get( 'ANGLE_instanced_arrays' ); - - extension.vertexAttribDivisorANGLE( attribute, 0 ); - attributeDivisors[ attribute ] = 0; - - } - - } - - function enableAttributeAndDivisor( attribute, meshPerAttribute ) { - - newAttributes[ attribute ] = 1; - - if ( enabledAttributes[ attribute ] === 0 ) { - - gl.enableVertexAttribArray( attribute ); - enabledAttributes[ attribute ] = 1; - - } - - if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { - - var extension = extensions.get( 'ANGLE_instanced_arrays' ); - - extension.vertexAttribDivisorANGLE( attribute, meshPerAttribute ); - attributeDivisors[ attribute ] = meshPerAttribute; - - } - - } - - function disableUnusedAttributes() { - - for ( var i = 0, l = enabledAttributes.length; i !== l; ++ i ) { - - if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { - - gl.disableVertexAttribArray( i ); - enabledAttributes[ i ] = 0; - - } - - } - - } - - function enable( id ) { - - if ( capabilities[ id ] !== true ) { - - gl.enable( id ); - capabilities[ id ] = true; - - } - - } - - function disable( id ) { - - if ( capabilities[ id ] !== false ) { - - gl.disable( id ); - capabilities[ id ] = false; - - } - - } - - function getCompressedTextureFormats() { - - if ( compressedTextureFormats === null ) { - - compressedTextureFormats = []; - - if ( extensions.get( 'WEBGL_compressed_texture_pvrtc' ) || - extensions.get( 'WEBGL_compressed_texture_s3tc' ) || - extensions.get( 'WEBGL_compressed_texture_etc1' ) ) { - - var formats = gl.getParameter( gl.COMPRESSED_TEXTURE_FORMATS ); - - for ( var i = 0; i < formats.length; i ++ ) { - - compressedTextureFormats.push( formats[ i ] ); - - } - - } - - } - - return compressedTextureFormats; - - } - - function useProgram( program ) { - - if ( currentProgram !== program ) { - - gl.useProgram( program ); - - currentProgram = program; - - return true; - - } - - return false; - - } - - function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) { - - if ( blending !== NoBlending ) { - - enable( gl.BLEND ); - - } else { - - disable( gl.BLEND ); - - } - - if ( blending !== CustomBlending ) { - - if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { - - switch ( blending ) { - - case AdditiveBlending: - - if ( premultipliedAlpha ) { - - gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD ); - gl.blendFuncSeparate( gl.ONE, gl.ONE, gl.ONE, gl.ONE ); - - } else { - - gl.blendEquation( gl.FUNC_ADD ); - gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); - - } - break; - - case SubtractiveBlending: - - if ( premultipliedAlpha ) { - - gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD ); - gl.blendFuncSeparate( gl.ZERO, gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ONE_MINUS_SRC_ALPHA ); - - } else { - - gl.blendEquation( gl.FUNC_ADD ); - gl.blendFunc( gl.ZERO, gl.ONE_MINUS_SRC_COLOR ); - - } - break; - - case MultiplyBlending: - - if ( premultipliedAlpha ) { - - gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD ); - gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA ); - - } else { - - gl.blendEquation( gl.FUNC_ADD ); - gl.blendFunc( gl.ZERO, gl.SRC_COLOR ); - - } - break; - - default: - - if ( premultipliedAlpha ) { - - gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD ); - gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - - } else { - - gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD ); - gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); - - } - - } - - } - - currentBlendEquation = null; - currentBlendSrc = null; - currentBlendDst = null; - currentBlendEquationAlpha = null; - currentBlendSrcAlpha = null; - currentBlendDstAlpha = null; - - } else { - - blendEquationAlpha = blendEquationAlpha || blendEquation; - blendSrcAlpha = blendSrcAlpha || blendSrc; - blendDstAlpha = blendDstAlpha || blendDst; - - if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { - - gl.blendEquationSeparate( utils.convert( blendEquation ), utils.convert( blendEquationAlpha ) ); - - currentBlendEquation = blendEquation; - currentBlendEquationAlpha = blendEquationAlpha; - - } - - if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { - - gl.blendFuncSeparate( utils.convert( blendSrc ), utils.convert( blendDst ), utils.convert( blendSrcAlpha ), utils.convert( blendDstAlpha ) ); - - currentBlendSrc = blendSrc; - currentBlendDst = blendDst; - currentBlendSrcAlpha = blendSrcAlpha; - currentBlendDstAlpha = blendDstAlpha; - - } - - } - - currentBlending = blending; - currentPremultipledAlpha = premultipliedAlpha; - - } - - function setMaterial( material ) { - - material.side === DoubleSide - ? disable( gl.CULL_FACE ) - : enable( gl.CULL_FACE ); - - setFlipSided( material.side === BackSide ); - - material.transparent === true - ? setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha ) - : setBlending( NoBlending ); - - depthBuffer.setFunc( material.depthFunc ); - depthBuffer.setTest( material.depthTest ); - depthBuffer.setMask( material.depthWrite ); - colorBuffer.setMask( material.colorWrite ); - - setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); - - } - - // - - function setFlipSided( flipSided ) { - - if ( currentFlipSided !== flipSided ) { - - if ( flipSided ) { - - gl.frontFace( gl.CW ); - - } else { - - gl.frontFace( gl.CCW ); - - } - - currentFlipSided = flipSided; - - } - - } - - function setCullFace( cullFace ) { - - if ( cullFace !== CullFaceNone ) { - - enable( gl.CULL_FACE ); - - if ( cullFace !== currentCullFace ) { - - if ( cullFace === CullFaceBack ) { - - gl.cullFace( gl.BACK ); - - } else if ( cullFace === CullFaceFront ) { - - gl.cullFace( gl.FRONT ); - - } else { - - gl.cullFace( gl.FRONT_AND_BACK ); - - } - - } - - } else { - - disable( gl.CULL_FACE ); - - } - - currentCullFace = cullFace; - - } - - function setLineWidth( width ) { - - if ( width !== currentLineWidth ) { - - if ( lineWidthAvailable ) gl.lineWidth( width ); - - currentLineWidth = width; - - } - - } - - function setPolygonOffset( polygonOffset, factor, units ) { - - if ( polygonOffset ) { - - enable( gl.POLYGON_OFFSET_FILL ); - - if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { - - gl.polygonOffset( factor, units ); - - currentPolygonOffsetFactor = factor; - currentPolygonOffsetUnits = units; - - } - - } else { - - disable( gl.POLYGON_OFFSET_FILL ); - - } - - } - - function setScissorTest( scissorTest ) { - - if ( scissorTest ) { - - enable( gl.SCISSOR_TEST ); - - } else { - - disable( gl.SCISSOR_TEST ); - - } - - } - - // texture - - function activeTexture( webglSlot ) { - - if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; - - if ( currentTextureSlot !== webglSlot ) { - - gl.activeTexture( webglSlot ); - currentTextureSlot = webglSlot; - - } - - } - - function bindTexture( webglType, webglTexture ) { - - if ( currentTextureSlot === null ) { - - activeTexture(); - - } - - var boundTexture = currentBoundTextures[ currentTextureSlot ]; - - if ( boundTexture === undefined ) { - - boundTexture = { type: undefined, texture: undefined }; - currentBoundTextures[ currentTextureSlot ] = boundTexture; - - } - - if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { - - gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); - - boundTexture.type = webglType; - boundTexture.texture = webglTexture; - - } - - } - - function compressedTexImage2D() { - - try { - - gl.compressedTexImage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - function texImage2D() { - - try { - - gl.texImage2D.apply( gl, arguments ); - - } catch ( error ) { - - console.error( 'THREE.WebGLState:', error ); - - } - - } - - // - - function scissor( scissor ) { - - if ( currentScissor.equals( scissor ) === false ) { - - gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); - currentScissor.copy( scissor ); - - } - - } - - function viewport( viewport ) { - - if ( currentViewport.equals( viewport ) === false ) { - - gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); - currentViewport.copy( viewport ); - - } - - } - - // - - function reset() { - - for ( var i = 0; i < enabledAttributes.length; i ++ ) { - - if ( enabledAttributes[ i ] === 1 ) { - - gl.disableVertexAttribArray( i ); - enabledAttributes[ i ] = 0; - - } - - } - - capabilities = {}; - - compressedTextureFormats = null; - - currentTextureSlot = null; - currentBoundTextures = {}; - - currentProgram = null; - - currentBlending = null; - - currentFlipSided = null; - currentCullFace = null; - - colorBuffer.reset(); - depthBuffer.reset(); - stencilBuffer.reset(); - - } - - return { - - buffers: { - color: colorBuffer, - depth: depthBuffer, - stencil: stencilBuffer - }, - - initAttributes: initAttributes, - enableAttribute: enableAttribute, - enableAttributeAndDivisor: enableAttributeAndDivisor, - disableUnusedAttributes: disableUnusedAttributes, - enable: enable, - disable: disable, - getCompressedTextureFormats: getCompressedTextureFormats, - - useProgram: useProgram, - - setBlending: setBlending, - setMaterial: setMaterial, - - setFlipSided: setFlipSided, - setCullFace: setCullFace, - - setLineWidth: setLineWidth, - setPolygonOffset: setPolygonOffset, - - setScissorTest: setScissorTest, - - activeTexture: activeTexture, - bindTexture: bindTexture, - compressedTexImage2D: compressedTexImage2D, - texImage2D: texImage2D, - - scissor: scissor, - viewport: viewport, - - reset: reset - - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLCapabilities( gl, extensions, parameters ) { - - var maxAnisotropy; - - function getMaxAnisotropy() { - - if ( maxAnisotropy !== undefined ) return maxAnisotropy; - - var extension = extensions.get( 'EXT_texture_filter_anisotropic' ); - - if ( extension !== null ) { - - maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); - - } else { - - maxAnisotropy = 0; - - } - - return maxAnisotropy; - - } - - function getMaxPrecision( precision ) { - - if ( precision === 'highp' ) { - - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { - - return 'highp'; - - } - - precision = 'mediump'; - - } - - if ( precision === 'mediump' ) { - - if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && - gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { - - return 'mediump'; - - } - - } - - return 'lowp'; - - } - - var precision = parameters.precision !== undefined ? parameters.precision : 'highp'; - var maxPrecision = getMaxPrecision( precision ); - - if ( maxPrecision !== precision ) { - - console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); - precision = maxPrecision; - - } - - var logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; - - var maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); - var maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); - var maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); - var maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); - - var maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); - var maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); - var maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); - var maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); - - var vertexTextures = maxVertexTextures > 0; - var floatFragmentTextures = !! extensions.get( 'OES_texture_float' ); - var floatVertexTextures = vertexTextures && floatFragmentTextures; - - return { - - getMaxAnisotropy: getMaxAnisotropy, - getMaxPrecision: getMaxPrecision, - - precision: precision, - logarithmicDepthBuffer: logarithmicDepthBuffer, - - maxTextures: maxTextures, - maxVertexTextures: maxVertexTextures, - maxTextureSize: maxTextureSize, - maxCubemapSize: maxCubemapSize, - - maxAttributes: maxAttributes, - maxVertexUniforms: maxVertexUniforms, - maxVaryings: maxVaryings, - maxFragmentUniforms: maxFragmentUniforms, - - vertexTextures: vertexTextures, - floatFragmentTextures: floatFragmentTextures, - floatVertexTextures: floatVertexTextures - - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - * @author greggman / http://games.greggman.com/ - * @author zz85 / http://www.lab4games.net/zz85/blog - * @author tschw - */ - -function PerspectiveCamera( fov, aspect, near, far ) { - - Camera.call( this ); - - this.type = 'PerspectiveCamera'; - - this.fov = fov !== undefined ? fov : 50; - this.zoom = 1; - - this.near = near !== undefined ? near : 0.1; - this.far = far !== undefined ? far : 2000; - this.focus = 10; - - this.aspect = aspect !== undefined ? aspect : 1; - this.view = null; - - this.filmGauge = 35; // width of the film (default in millimeters) - this.filmOffset = 0; // horizontal film offset (same unit as gauge) - - this.updateProjectionMatrix(); - -} - -PerspectiveCamera.prototype = Object.assign( Object.create( Camera.prototype ), { - - constructor: PerspectiveCamera, - - isPerspectiveCamera: true, - - copy: function ( source, recursive ) { - - Camera.prototype.copy.call( this, source, recursive ); - - this.fov = source.fov; - this.zoom = source.zoom; - - this.near = source.near; - this.far = source.far; - this.focus = source.focus; - - this.aspect = source.aspect; - this.view = source.view === null ? null : Object.assign( {}, source.view ); - - this.filmGauge = source.filmGauge; - this.filmOffset = source.filmOffset; - - return this; - - }, - - /** - * Sets the FOV by focal length in respect to the current .filmGauge. - * - * The default film gauge is 35, so that the focal length can be specified for - * a 35mm (full frame) camera. - * - * Values for focal length and film gauge must have the same unit. - */ - setFocalLength: function ( focalLength ) { - - // see http://www.bobatkins.com/photography/technical/field_of_view.html - var vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; - - this.fov = _Math.RAD2DEG * 2 * Math.atan( vExtentSlope ); - this.updateProjectionMatrix(); - - }, - - /** - * Calculates the focal length from the current .fov and .filmGauge. - */ - getFocalLength: function () { - - var vExtentSlope = Math.tan( _Math.DEG2RAD * 0.5 * this.fov ); - - return 0.5 * this.getFilmHeight() / vExtentSlope; - - }, - - getEffectiveFOV: function () { - - return _Math.RAD2DEG * 2 * Math.atan( - Math.tan( _Math.DEG2RAD * 0.5 * this.fov ) / this.zoom ); - - }, - - getFilmWidth: function () { - - // film not completely covered in portrait format (aspect < 1) - return this.filmGauge * Math.min( this.aspect, 1 ); - - }, - - getFilmHeight: function () { - - // film not completely covered in landscape format (aspect > 1) - return this.filmGauge / Math.max( this.aspect, 1 ); - - }, - - /** - * Sets an offset in a larger frustum. This is useful for multi-window or - * multi-monitor/multi-machine setups. - * - * For example, if you have 3x2 monitors and each monitor is 1920x1080 and - * the monitors are in grid like this - * - * +---+---+---+ - * | A | B | C | - * +---+---+---+ - * | D | E | F | - * +---+---+---+ - * - * then for each monitor you would call it like this - * - * var w = 1920; - * var h = 1080; - * var fullWidth = w * 3; - * var fullHeight = h * 2; - * - * --A-- - * camera.setOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); - * --B-- - * camera.setOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); - * --C-- - * camera.setOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); - * --D-- - * camera.setOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); - * --E-- - * camera.setOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); - * --F-- - * camera.setOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); - * - * Note there is no reason monitors have to be the same size or in a grid. - */ - setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) { - - this.aspect = fullWidth / fullHeight; - - if ( this.view === null ) { - - this.view = { - enabled: true, - fullWidth: 1, - fullHeight: 1, - offsetX: 0, - offsetY: 0, - width: 1, - height: 1 - }; - - } - - this.view.enabled = true; - this.view.fullWidth = fullWidth; - this.view.fullHeight = fullHeight; - this.view.offsetX = x; - this.view.offsetY = y; - this.view.width = width; - this.view.height = height; - - this.updateProjectionMatrix(); - - }, - - clearViewOffset: function () { - - if ( this.view !== null ) { - - this.view.enabled = false; - - } - - this.updateProjectionMatrix(); - - }, - - updateProjectionMatrix: function () { - - var near = this.near, - top = near * Math.tan( - _Math.DEG2RAD * 0.5 * this.fov ) / this.zoom, - height = 2 * top, - width = this.aspect * height, - left = - 0.5 * width, - view = this.view; - - if ( this.view !== null && this.view.enabled ) { - - var fullWidth = view.fullWidth, - fullHeight = view.fullHeight; - - left += view.offsetX * width / fullWidth; - top -= view.offsetY * height / fullHeight; - width *= view.width / fullWidth; - height *= view.height / fullHeight; - - } - - var skew = this.filmOffset; - if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); - - this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far ); - - }, - - toJSON: function ( meta ) { - - var data = Object3D.prototype.toJSON.call( this, meta ); - - data.object.fov = this.fov; - data.object.zoom = this.zoom; - - data.object.near = this.near; - data.object.far = this.far; - data.object.focus = this.focus; - - data.object.aspect = this.aspect; - - if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); - - data.object.filmGauge = this.filmGauge; - data.object.filmOffset = this.filmOffset; - - return data; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function ArrayCamera( array ) { - - PerspectiveCamera.call( this ); - - this.cameras = array || []; - -} - -ArrayCamera.prototype = Object.assign( Object.create( PerspectiveCamera.prototype ), { - - constructor: ArrayCamera, - - isArrayCamera: true - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebVRManager( renderer ) { - - var scope = this; - - var device = null; - var frameData = null; - - if ( typeof window !== 'undefined' && 'VRFrameData' in window ) { - - frameData = new window.VRFrameData(); - - } - - var matrixWorldInverse = new Matrix4(); - - var standingMatrix = new Matrix4(); - var standingMatrixInverse = new Matrix4(); - - var cameraL = new PerspectiveCamera(); - cameraL.bounds = new Vector4( 0.0, 0.0, 0.5, 1.0 ); - cameraL.layers.enable( 1 ); - - var cameraR = new PerspectiveCamera(); - cameraR.bounds = new Vector4( 0.5, 0.0, 0.5, 1.0 ); - cameraR.layers.enable( 2 ); - - var cameraVR = new ArrayCamera( [ cameraL, cameraR ] ); - cameraVR.layers.enable( 1 ); - cameraVR.layers.enable( 2 ); - - // - - var currentSize, currentPixelRatio; - - function onVRDisplayPresentChange() { - - if ( device !== null && device.isPresenting ) { - - var eyeParameters = device.getEyeParameters( 'left' ); - var renderWidth = eyeParameters.renderWidth; - var renderHeight = eyeParameters.renderHeight; - - currentPixelRatio = renderer.getPixelRatio(); - currentSize = renderer.getSize(); - - renderer.setDrawingBufferSize( renderWidth * 2, renderHeight, 1 ); - - } else if ( scope.enabled ) { - - renderer.setDrawingBufferSize( currentSize.width, currentSize.height, currentPixelRatio ); - - } - - } - - if ( typeof window !== 'undefined' ) { - - window.addEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false ); - - } - - // - - this.enabled = false; - this.standing = false; - - this.getDevice = function () { - - return device; - - }; - - this.setDevice = function ( value ) { - - if ( value !== undefined ) device = value; - - }; - - this.getCamera = function ( camera ) { - - if ( device === null ) return camera; - - device.depthNear = camera.near; - device.depthFar = camera.far; - - device.getFrameData( frameData ); - - // - - var pose = frameData.pose; - - if ( pose.position !== null ) { - - camera.position.fromArray( pose.position ); - - } else { - - camera.position.set( 0, 0, 0 ); - - } - - if ( pose.orientation !== null ) { - - camera.quaternion.fromArray( pose.orientation ); - - } - - camera.updateMatrixWorld(); - - var stageParameters = device.stageParameters; - - if ( this.standing && stageParameters ) { - - standingMatrix.fromArray( stageParameters.sittingToStandingTransform ); - standingMatrixInverse.getInverse( standingMatrix ); - - camera.matrixWorld.multiply( standingMatrix ); - camera.matrixWorldInverse.multiply( standingMatrixInverse ); - - } - - if ( device.isPresenting === false ) return camera; - - // - - cameraL.near = camera.near; - cameraR.near = camera.near; - - cameraL.far = camera.far; - cameraR.far = camera.far; - - cameraVR.matrixWorld.copy( camera.matrixWorld ); - cameraVR.matrixWorldInverse.copy( camera.matrixWorldInverse ); - - cameraL.matrixWorldInverse.fromArray( frameData.leftViewMatrix ); - cameraR.matrixWorldInverse.fromArray( frameData.rightViewMatrix ); - - if ( this.standing && stageParameters ) { - - cameraL.matrixWorldInverse.multiply( standingMatrixInverse ); - cameraR.matrixWorldInverse.multiply( standingMatrixInverse ); - - } - - var parent = camera.parent; - - if ( parent !== null ) { - - matrixWorldInverse.getInverse( parent.matrixWorld ); - - cameraL.matrixWorldInverse.multiply( matrixWorldInverse ); - cameraR.matrixWorldInverse.multiply( matrixWorldInverse ); - - } - - // envMap and Mirror needs camera.matrixWorld - - cameraL.matrixWorld.getInverse( cameraL.matrixWorldInverse ); - cameraR.matrixWorld.getInverse( cameraR.matrixWorldInverse ); - - cameraL.projectionMatrix.fromArray( frameData.leftProjectionMatrix ); - cameraR.projectionMatrix.fromArray( frameData.rightProjectionMatrix ); - - // HACK @mrdoob - // https://github.com/w3c/webvr/issues/203 - - cameraVR.projectionMatrix.copy( cameraL.projectionMatrix ); - - // - - var layers = device.getLayers(); - - if ( layers.length ) { - - var layer = layers[ 0 ]; - - if ( layer.leftBounds !== null && layer.leftBounds.length === 4 ) { - - cameraL.bounds.fromArray( layer.leftBounds ); - - } - - if ( layer.rightBounds !== null && layer.rightBounds.length === 4 ) { - - cameraR.bounds.fromArray( layer.rightBounds ); - - } - - } - - return cameraVR; - - }; - - this.getStandingMatrix = function () { - - return standingMatrix; - - }; - - this.submitFrame = function () { - - if ( device && device.isPresenting ) device.submitFrame(); - - }; - - this.dispose = function () { - - if ( typeof window !== 'undefined' ) { - - window.removeEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange ); - - } - - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function WebGLExtensions( gl ) { - - var extensions = {}; - - return { - - get: function ( name ) { - - if ( extensions[ name ] !== undefined ) { - - return extensions[ name ]; - - } - - var extension; - - switch ( name ) { - - case 'WEBGL_depth_texture': - extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' ); - break; - - case 'EXT_texture_filter_anisotropic': - extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); - break; - - case 'WEBGL_compressed_texture_s3tc': - extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); - break; - - case 'WEBGL_compressed_texture_pvrtc': - extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); - break; - - case 'WEBGL_compressed_texture_etc1': - extension = gl.getExtension( 'WEBGL_compressed_texture_etc1' ); - break; - - default: - extension = gl.getExtension( name ); - - } - - if ( extension === null ) { - - console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); - - } - - extensions[ name ] = extension; - - return extension; - - } - - }; - -} - -/** - * @author tschw - */ - -function WebGLClipping() { - - var scope = this, - - globalState = null, - numGlobalPlanes = 0, - localClippingEnabled = false, - renderingShadows = false, - - plane = new Plane(), - viewNormalMatrix = new Matrix3(), - - uniform = { value: null, needsUpdate: false }; - - this.uniform = uniform; - this.numPlanes = 0; - this.numIntersection = 0; - - this.init = function ( planes, enableLocalClipping, camera ) { - - var enabled = - planes.length !== 0 || - enableLocalClipping || - // enable state of previous frame - the clipping code has to - // run another frame in order to reset the state: - numGlobalPlanes !== 0 || - localClippingEnabled; - - localClippingEnabled = enableLocalClipping; - - globalState = projectPlanes( planes, camera, 0 ); - numGlobalPlanes = planes.length; - - return enabled; - - }; - - this.beginShadows = function () { - - renderingShadows = true; - projectPlanes( null ); - - }; - - this.endShadows = function () { - - renderingShadows = false; - resetGlobalState(); - - }; - - this.setState = function ( planes, clipIntersection, clipShadows, camera, cache, fromCache ) { - - if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { - - // there's no local clipping - - if ( renderingShadows ) { - - // there's no global clipping - - projectPlanes( null ); - - } else { - - resetGlobalState(); - - } - - } else { - - var nGlobal = renderingShadows ? 0 : numGlobalPlanes, - lGlobal = nGlobal * 4, - - dstArray = cache.clippingState || null; - - uniform.value = dstArray; // ensure unique state - - dstArray = projectPlanes( planes, camera, lGlobal, fromCache ); - - for ( var i = 0; i !== lGlobal; ++ i ) { - - dstArray[ i ] = globalState[ i ]; - - } - - cache.clippingState = dstArray; - this.numIntersection = clipIntersection ? this.numPlanes : 0; - this.numPlanes += nGlobal; - - } - - - }; - - function resetGlobalState() { - - if ( uniform.value !== globalState ) { - - uniform.value = globalState; - uniform.needsUpdate = numGlobalPlanes > 0; - - } - - scope.numPlanes = numGlobalPlanes; - scope.numIntersection = 0; - - } - - function projectPlanes( planes, camera, dstOffset, skipTransform ) { - - var nPlanes = planes !== null ? planes.length : 0, - dstArray = null; - - if ( nPlanes !== 0 ) { - - dstArray = uniform.value; - - if ( skipTransform !== true || dstArray === null ) { - - var flatSize = dstOffset + nPlanes * 4, - viewMatrix = camera.matrixWorldInverse; - - viewNormalMatrix.getNormalMatrix( viewMatrix ); - - if ( dstArray === null || dstArray.length < flatSize ) { - - dstArray = new Float32Array( flatSize ); - - } - - for ( var i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { - - plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); - - plane.normal.toArray( dstArray, i4 ); - dstArray[ i4 + 3 ] = plane.constant; - - } - - } - - uniform.value = dstArray; - uniform.needsUpdate = true; - - } - - scope.numPlanes = nPlanes; - - return dstArray; - - } - -} - -/** - * @author thespite / http://www.twitter.com/thespite - */ - -function WebGLUtils( gl, extensions ) { - - function convert( p ) { - - var extension; - - if ( p === RepeatWrapping ) return gl.REPEAT; - if ( p === ClampToEdgeWrapping ) return gl.CLAMP_TO_EDGE; - if ( p === MirroredRepeatWrapping ) return gl.MIRRORED_REPEAT; - - if ( p === NearestFilter ) return gl.NEAREST; - if ( p === NearestMipMapNearestFilter ) return gl.NEAREST_MIPMAP_NEAREST; - if ( p === NearestMipMapLinearFilter ) return gl.NEAREST_MIPMAP_LINEAR; - - if ( p === LinearFilter ) return gl.LINEAR; - if ( p === LinearMipMapNearestFilter ) return gl.LINEAR_MIPMAP_NEAREST; - if ( p === LinearMipMapLinearFilter ) return gl.LINEAR_MIPMAP_LINEAR; - - if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; - if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; - if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; - if ( p === UnsignedShort565Type ) return gl.UNSIGNED_SHORT_5_6_5; - - if ( p === ByteType ) return gl.BYTE; - if ( p === ShortType ) return gl.SHORT; - if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; - if ( p === IntType ) return gl.INT; - if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; - if ( p === FloatType ) return gl.FLOAT; - - if ( p === HalfFloatType ) { - - extension = extensions.get( 'OES_texture_half_float' ); - - if ( extension !== null ) return extension.HALF_FLOAT_OES; - - } - - if ( p === AlphaFormat ) return gl.ALPHA; - if ( p === RGBFormat ) return gl.RGB; - if ( p === RGBAFormat ) return gl.RGBA; - if ( p === LuminanceFormat ) return gl.LUMINANCE; - if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA; - if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; - if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; - - if ( p === AddEquation ) return gl.FUNC_ADD; - if ( p === SubtractEquation ) return gl.FUNC_SUBTRACT; - if ( p === ReverseSubtractEquation ) return gl.FUNC_REVERSE_SUBTRACT; - - if ( p === ZeroFactor ) return gl.ZERO; - if ( p === OneFactor ) return gl.ONE; - if ( p === SrcColorFactor ) return gl.SRC_COLOR; - if ( p === OneMinusSrcColorFactor ) return gl.ONE_MINUS_SRC_COLOR; - if ( p === SrcAlphaFactor ) return gl.SRC_ALPHA; - if ( p === OneMinusSrcAlphaFactor ) return gl.ONE_MINUS_SRC_ALPHA; - if ( p === DstAlphaFactor ) return gl.DST_ALPHA; - if ( p === OneMinusDstAlphaFactor ) return gl.ONE_MINUS_DST_ALPHA; - - if ( p === DstColorFactor ) return gl.DST_COLOR; - if ( p === OneMinusDstColorFactor ) return gl.ONE_MINUS_DST_COLOR; - if ( p === SrcAlphaSaturateFactor ) return gl.SRC_ALPHA_SATURATE; - - if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || - p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); - - if ( extension !== null ) { - - if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; - if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; - if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; - - } - - } - - if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || - p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); - - if ( extension !== null ) { - - if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; - if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; - if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; - if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; - - } - - } - - if ( p === RGB_ETC1_Format ) { - - extension = extensions.get( 'WEBGL_compressed_texture_etc1' ); - - if ( extension !== null ) return extension.COMPRESSED_RGB_ETC1_WEBGL; - - } - - if ( p === MinEquation || p === MaxEquation ) { - - extension = extensions.get( 'EXT_blend_minmax' ); - - if ( extension !== null ) { - - if ( p === MinEquation ) return extension.MIN_EXT; - if ( p === MaxEquation ) return extension.MAX_EXT; - - } - - } - - if ( p === UnsignedInt248Type ) { - - extension = extensions.get( 'WEBGL_depth_texture' ); - - if ( extension !== null ) return extension.UNSIGNED_INT_24_8_WEBGL; - - } - - return 0; - - } - - return { convert: convert }; - -} - -/** - * @author supereggbert / http://www.paulbrunt.co.uk/ - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * @author szimek / https://github.com/szimek/ - * @author tschw - */ - -function WebGLRenderer( parameters ) { - - console.log( 'THREE.WebGLRenderer', REVISION ); - - parameters = parameters || {}; - - var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ), - _context = parameters.context !== undefined ? parameters.context : null, - - _alpha = parameters.alpha !== undefined ? parameters.alpha : false, - _depth = parameters.depth !== undefined ? parameters.depth : true, - _stencil = parameters.stencil !== undefined ? parameters.stencil : true, - _antialias = parameters.antialias !== undefined ? parameters.antialias : false, - _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true, - _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false; - - var lightsArray = []; - var shadowsArray = []; - - var currentRenderList = null; - - var spritesArray = []; - var flaresArray = []; - - // public properties - - this.domElement = _canvas; - this.context = null; - - // clearing - - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; - - // scene graph - - this.sortObjects = true; - - // user-defined clipping - - this.clippingPlanes = []; - this.localClippingEnabled = false; - - // physically based shading - - this.gammaFactor = 2.0; // for backwards compatibility - this.gammaInput = false; - this.gammaOutput = false; - - // physical lights - - this.physicallyCorrectLights = false; - - // tone mapping - - this.toneMapping = LinearToneMapping; - this.toneMappingExposure = 1.0; - this.toneMappingWhitePoint = 1.0; - - // morphs - - this.maxMorphTargets = 8; - this.maxMorphNormals = 4; - - // internal properties - - var _this = this, - - _isContextLost = false, - - // internal state cache - - _currentRenderTarget = null, - _currentFramebuffer = null, - _currentMaterialId = - 1, - _currentGeometryProgram = '', - - _currentCamera = null, - _currentArrayCamera = null, - - _currentViewport = new Vector4(), - _currentScissor = new Vector4(), - _currentScissorTest = null, - - // - - _usedTextureUnits = 0, - - // - - _width = _canvas.width, - _height = _canvas.height, - - _pixelRatio = 1, - - _viewport = new Vector4( 0, 0, _width, _height ), - _scissor = new Vector4( 0, 0, _width, _height ), - _scissorTest = false, - - // frustum - - _frustum = new Frustum(), - - // clipping - - _clipping = new WebGLClipping(), - _clippingEnabled = false, - _localClippingEnabled = false, - - // camera matrices cache - - _projScreenMatrix = new Matrix4(), - - _vector3 = new Vector3(), - - // info - - _infoMemory = { - geometries: 0, - textures: 0 - }, - - _infoRender = { - - frame: 0, - calls: 0, - vertices: 0, - faces: 0, - points: 0 - - }; - - this.info = { - - render: _infoRender, - memory: _infoMemory, - programs: null - - }; - - function getTargetPixelRatio() { - - return _currentRenderTarget === null ? _pixelRatio : 1; - - } - - // initialize - - var _gl; - - try { - - var contextAttributes = { - alpha: _alpha, - depth: _depth, - stencil: _stencil, - antialias: _antialias, - premultipliedAlpha: _premultipliedAlpha, - preserveDrawingBuffer: _preserveDrawingBuffer - }; - - _gl = _context || _canvas.getContext( 'webgl', contextAttributes ) || _canvas.getContext( 'experimental-webgl', contextAttributes ); - - if ( _gl === null ) { - - if ( _canvas.getContext( 'webgl' ) !== null ) { - - throw 'Error creating WebGL context with your selected attributes.'; - - } else { - - throw 'Error creating WebGL context.'; - - } - - } - - // Some experimental-webgl implementations do not have getShaderPrecisionFormat - - if ( _gl.getShaderPrecisionFormat === undefined ) { - - _gl.getShaderPrecisionFormat = function () { - - return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 }; - - }; - - } - - _canvas.addEventListener( 'webglcontextlost', onContextLost, false ); - _canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); - - } catch ( error ) { - - console.error( 'THREE.WebGLRenderer: ' + error ); - - } - - var extensions, capabilities, state; - var properties, textures, attributes, geometries, objects, lights; - var programCache, renderLists; - - var background, morphtargets, bufferRenderer, indexedBufferRenderer; - var flareRenderer, spriteRenderer; - - var utils; - - function initGLContext() { - - extensions = new WebGLExtensions( _gl ); - extensions.get( 'WEBGL_depth_texture' ); - extensions.get( 'OES_texture_float' ); - extensions.get( 'OES_texture_float_linear' ); - extensions.get( 'OES_texture_half_float' ); - extensions.get( 'OES_texture_half_float_linear' ); - extensions.get( 'OES_standard_derivatives' ); - extensions.get( 'OES_element_index_uint' ); - extensions.get( 'ANGLE_instanced_arrays' ); - - utils = new WebGLUtils( _gl, extensions ); - - capabilities = new WebGLCapabilities( _gl, extensions, parameters ); - - state = new WebGLState( _gl, extensions, utils ); - state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ) ); - state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ) ); - - properties = new WebGLProperties(); - textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, _infoMemory ); - attributes = new WebGLAttributes( _gl ); - geometries = new WebGLGeometries( _gl, attributes, _infoMemory ); - objects = new WebGLObjects( geometries, _infoRender ); - morphtargets = new WebGLMorphtargets( _gl ); - programCache = new WebGLPrograms( _this, extensions, capabilities ); - lights = new WebGLLights(); - renderLists = new WebGLRenderLists(); - - background = new WebGLBackground( _this, state, geometries, _premultipliedAlpha ); - - bufferRenderer = new WebGLBufferRenderer( _gl, extensions, _infoRender ); - indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, _infoRender ); - - flareRenderer = new WebGLFlareRenderer( _this, _gl, state, textures, capabilities ); - spriteRenderer = new WebGLSpriteRenderer( _this, _gl, state, textures, capabilities ); - - _this.info.programs = programCache.programs; - - _this.context = _gl; - _this.capabilities = capabilities; - _this.extensions = extensions; - _this.properties = properties; - _this.renderLists = renderLists; - _this.state = state; - - } - - initGLContext(); - - // vr - - var vr = new WebVRManager( _this ); - - this.vr = vr; - - // shadow map - - var shadowMap = new WebGLShadowMap( _this, objects, capabilities.maxTextureSize ); - - this.shadowMap = shadowMap; - - // API - - this.getContext = function () { - - return _gl; - - }; - - this.getContextAttributes = function () { - - return _gl.getContextAttributes(); - - }; - - this.forceContextLoss = function () { - - var extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.loseContext(); - - }; - - this.forceContextRestore = function () { - - var extension = extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.restoreContext(); - - }; - - this.getPixelRatio = function () { - - return _pixelRatio; - - }; - - this.setPixelRatio = function ( value ) { - - if ( value === undefined ) return; - - _pixelRatio = value; - - this.setSize( _width, _height, false ); - - }; - - this.getSize = function () { - - return { - width: _width, - height: _height - }; - - }; - - this.setSize = function ( width, height, updateStyle ) { - - var device = vr.getDevice(); - - if ( device && device.isPresenting ) { - - console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); - return; - - } - - _width = width; - _height = height; - - _canvas.width = width * _pixelRatio; - _canvas.height = height * _pixelRatio; - - if ( updateStyle !== false ) { - - _canvas.style.width = width + 'px'; - _canvas.style.height = height + 'px'; - - } - - this.setViewport( 0, 0, width, height ); - - }; - - this.getDrawingBufferSize = function () { - - return { - width: _width * _pixelRatio, - height: _height * _pixelRatio - }; - - }; - - this.setDrawingBufferSize = function ( width, height, pixelRatio ) { - - _width = width; - _height = height; - - _pixelRatio = pixelRatio; - - _canvas.width = width * pixelRatio; - _canvas.height = height * pixelRatio; - - this.setViewport( 0, 0, width, height ); - - }; - - this.setViewport = function ( x, y, width, height ) { - - _viewport.set( x, _height - y - height, width, height ); - state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ) ); - - }; - - this.setScissor = function ( x, y, width, height ) { - - _scissor.set( x, _height - y - height, width, height ); - state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ) ); - - }; - - this.setScissorTest = function ( boolean ) { - - state.setScissorTest( _scissorTest = boolean ); - - }; - - // Clearing - - this.getClearColor = function () { - - return background.getClearColor(); - - }; - - this.setClearColor = function () { - - background.setClearColor.apply( background, arguments ); - - }; - - this.getClearAlpha = function () { - - return background.getClearAlpha(); - - }; - - this.setClearAlpha = function () { - - background.setClearAlpha.apply( background, arguments ); - - }; - - this.clear = function ( color, depth, stencil ) { - - var bits = 0; - - if ( color === undefined || color ) bits |= _gl.COLOR_BUFFER_BIT; - if ( depth === undefined || depth ) bits |= _gl.DEPTH_BUFFER_BIT; - if ( stencil === undefined || stencil ) bits |= _gl.STENCIL_BUFFER_BIT; - - _gl.clear( bits ); - - }; - - this.clearColor = function () { - - this.clear( true, false, false ); - - }; - - this.clearDepth = function () { - - this.clear( false, true, false ); - - }; - - this.clearStencil = function () { - - this.clear( false, false, true ); - - }; - - this.clearTarget = function ( renderTarget, color, depth, stencil ) { - - this.setRenderTarget( renderTarget ); - this.clear( color, depth, stencil ); - - }; - - // - - this.dispose = function () { - - _canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); - _canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); - - renderLists.dispose(); - - vr.dispose(); - - }; - - // Events - - function onContextLost( event ) { - - event.preventDefault(); - - console.log( 'THREE.WebGLRenderer: Context Lost.' ); - - _isContextLost = true; - - } - - function onContextRestore( /* event */ ) { - - console.log( 'THREE.WebGLRenderer: Context Restored.' ); - - _isContextLost = false; - - initGLContext(); - - } - - function onMaterialDispose( event ) { - - var material = event.target; - - material.removeEventListener( 'dispose', onMaterialDispose ); - - deallocateMaterial( material ); - - } - - // Buffer deallocation - - function deallocateMaterial( material ) { - - releaseMaterialProgramReference( material ); - - properties.remove( material ); - - } - - - function releaseMaterialProgramReference( material ) { - - var programInfo = properties.get( material ).program; - - material.program = undefined; - - if ( programInfo !== undefined ) { - - programCache.releaseProgram( programInfo ); - - } - - } - - // Buffer rendering - - function renderObjectImmediate( object, program, material ) { - - object.render( function ( object ) { - - _this.renderBufferImmediate( object, program, material ); - - } ); - - } - - this.renderBufferImmediate = function ( object, program, material ) { - - state.initAttributes(); - - var buffers = properties.get( object ); - - if ( object.hasPositions && ! buffers.position ) buffers.position = _gl.createBuffer(); - if ( object.hasNormals && ! buffers.normal ) buffers.normal = _gl.createBuffer(); - if ( object.hasUvs && ! buffers.uv ) buffers.uv = _gl.createBuffer(); - if ( object.hasColors && ! buffers.color ) buffers.color = _gl.createBuffer(); - - var programAttributes = program.getAttributes(); - - if ( object.hasPositions ) { - - _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.position ); - _gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW ); - - state.enableAttribute( programAttributes.position ); - _gl.vertexAttribPointer( programAttributes.position, 3, _gl.FLOAT, false, 0, 0 ); - - } - - if ( object.hasNormals ) { - - _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.normal ); - - if ( ! material.isMeshPhongMaterial && - ! material.isMeshStandardMaterial && - ! material.isMeshNormalMaterial && - material.flatShading === true ) { - - for ( var i = 0, l = object.count * 3; i < l; i += 9 ) { - - var array = object.normalArray; - - var nx = ( array[ i + 0 ] + array[ i + 3 ] + array[ i + 6 ] ) / 3; - var ny = ( array[ i + 1 ] + array[ i + 4 ] + array[ i + 7 ] ) / 3; - var nz = ( array[ i + 2 ] + array[ i + 5 ] + array[ i + 8 ] ) / 3; - - array[ i + 0 ] = nx; - array[ i + 1 ] = ny; - array[ i + 2 ] = nz; - - array[ i + 3 ] = nx; - array[ i + 4 ] = ny; - array[ i + 5 ] = nz; - - array[ i + 6 ] = nx; - array[ i + 7 ] = ny; - array[ i + 8 ] = nz; - - } - - } - - _gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW ); - - state.enableAttribute( programAttributes.normal ); - - _gl.vertexAttribPointer( programAttributes.normal, 3, _gl.FLOAT, false, 0, 0 ); - - } - - if ( object.hasUvs && material.map ) { - - _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.uv ); - _gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW ); - - state.enableAttribute( programAttributes.uv ); - - _gl.vertexAttribPointer( programAttributes.uv, 2, _gl.FLOAT, false, 0, 0 ); - - } - - if ( object.hasColors && material.vertexColors !== NoColors ) { - - _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.color ); - _gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW ); - - state.enableAttribute( programAttributes.color ); - - _gl.vertexAttribPointer( programAttributes.color, 3, _gl.FLOAT, false, 0, 0 ); - - } - - state.disableUnusedAttributes(); - - _gl.drawArrays( _gl.TRIANGLES, 0, object.count ); - - object.count = 0; - - }; - - this.renderBufferDirect = function ( camera, fog, geometry, material, object, group ) { - - state.setMaterial( material ); - - var program = setProgram( camera, fog, material, object ); - var geometryProgram = geometry.id + '_' + program.id + '_' + ( material.wireframe === true ); - - var updateBuffers = false; - - if ( geometryProgram !== _currentGeometryProgram ) { - - _currentGeometryProgram = geometryProgram; - updateBuffers = true; - - } - - if ( object.morphTargetInfluences ) { - - morphtargets.update( object, geometry, material, program ); - - updateBuffers = true; - - } - - // - - var index = geometry.index; - var position = geometry.attributes.position; - var rangeFactor = 1; - - if ( material.wireframe === true ) { - - index = geometries.getWireframeAttribute( geometry ); - rangeFactor = 2; - - } - - var attribute; - var renderer = bufferRenderer; - - if ( index !== null ) { - - attribute = attributes.get( index ); - - renderer = indexedBufferRenderer; - renderer.setIndex( attribute ); - - } - - if ( updateBuffers ) { - - setupVertexAttributes( material, program, geometry ); - - if ( index !== null ) { - - _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attribute.buffer ); - - } - - } - - // - - var dataCount = 0; - - if ( index !== null ) { - - dataCount = index.count; - - } else if ( position !== undefined ) { - - dataCount = position.count; - - } - - var rangeStart = geometry.drawRange.start * rangeFactor; - var rangeCount = geometry.drawRange.count * rangeFactor; - - var groupStart = group !== null ? group.start * rangeFactor : 0; - var groupCount = group !== null ? group.count * rangeFactor : Infinity; - - var drawStart = Math.max( rangeStart, groupStart ); - var drawEnd = Math.min( dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1; - - var drawCount = Math.max( 0, drawEnd - drawStart + 1 ); - - if ( drawCount === 0 ) return; - - // - - if ( object.isMesh ) { - - if ( material.wireframe === true ) { - - state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); - renderer.setMode( _gl.LINES ); - - } else { - - switch ( object.drawMode ) { - - case TrianglesDrawMode: - renderer.setMode( _gl.TRIANGLES ); - break; - - case TriangleStripDrawMode: - renderer.setMode( _gl.TRIANGLE_STRIP ); - break; - - case TriangleFanDrawMode: - renderer.setMode( _gl.TRIANGLE_FAN ); - break; - - } - - } - - - } else if ( object.isLine ) { - - var lineWidth = material.linewidth; - - if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material - - state.setLineWidth( lineWidth * getTargetPixelRatio() ); - - if ( object.isLineSegments ) { - - renderer.setMode( _gl.LINES ); - - } else if ( object.isLineLoop ) { - - renderer.setMode( _gl.LINE_LOOP ); - - } else { - - renderer.setMode( _gl.LINE_STRIP ); - - } - - } else if ( object.isPoints ) { - - renderer.setMode( _gl.POINTS ); - - } - - if ( geometry && geometry.isInstancedBufferGeometry ) { - - if ( geometry.maxInstancedCount > 0 ) { - - renderer.renderInstances( geometry, drawStart, drawCount ); - - } - - } else { - - renderer.render( drawStart, drawCount ); - - } - - }; - - function setupVertexAttributes( material, program, geometry, startIndex ) { - - if ( geometry && geometry.isInstancedBufferGeometry ) { - - if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) { - - console.error( 'THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); - return; - - } - - } - - if ( startIndex === undefined ) startIndex = 0; - - state.initAttributes(); - - var geometryAttributes = geometry.attributes; - - var programAttributes = program.getAttributes(); - - var materialDefaultAttributeValues = material.defaultAttributeValues; - - for ( var name in programAttributes ) { - - var programAttribute = programAttributes[ name ]; - - if ( programAttribute >= 0 ) { - - var geometryAttribute = geometryAttributes[ name ]; - - if ( geometryAttribute !== undefined ) { - - var normalized = geometryAttribute.normalized; - var size = geometryAttribute.itemSize; - - var attribute = attributes.get( geometryAttribute ); - - // TODO Attribute may not be available on context restore - - if ( attribute === undefined ) continue; - - var buffer = attribute.buffer; - var type = attribute.type; - var bytesPerElement = attribute.bytesPerElement; - - if ( geometryAttribute.isInterleavedBufferAttribute ) { - - var data = geometryAttribute.data; - var stride = data.stride; - var offset = geometryAttribute.offset; - - if ( data && data.isInstancedInterleavedBuffer ) { - - state.enableAttributeAndDivisor( programAttribute, data.meshPerAttribute ); - - if ( geometry.maxInstancedCount === undefined ) { - - geometry.maxInstancedCount = data.meshPerAttribute * data.count; - - } - - } else { - - state.enableAttribute( programAttribute ); - - } - - _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer ); - _gl.vertexAttribPointer( programAttribute, size, type, normalized, stride * bytesPerElement, ( startIndex * stride + offset ) * bytesPerElement ); - - } else { - - if ( geometryAttribute.isInstancedBufferAttribute ) { - - state.enableAttributeAndDivisor( programAttribute, geometryAttribute.meshPerAttribute ); - - if ( geometry.maxInstancedCount === undefined ) { - - geometry.maxInstancedCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; - - } - - } else { - - state.enableAttribute( programAttribute ); - - } - - _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer ); - _gl.vertexAttribPointer( programAttribute, size, type, normalized, 0, startIndex * size * bytesPerElement ); - - } - - } else if ( materialDefaultAttributeValues !== undefined ) { - - var value = materialDefaultAttributeValues[ name ]; - - if ( value !== undefined ) { - - switch ( value.length ) { - - case 2: - _gl.vertexAttrib2fv( programAttribute, value ); - break; - - case 3: - _gl.vertexAttrib3fv( programAttribute, value ); - break; - - case 4: - _gl.vertexAttrib4fv( programAttribute, value ); - break; - - default: - _gl.vertexAttrib1fv( programAttribute, value ); - - } - - } - - } - - } - - } - - state.disableUnusedAttributes(); - - } - - // Compile - - this.compile = function ( scene, camera ) { - - lightsArray.length = 0; - shadowsArray.length = 0; - - scene.traverse( function ( object ) { - - if ( object.isLight ) { - - lightsArray.push( object ); - - if ( object.castShadow ) { - - shadowsArray.push( object ); - - } - - } - - } ); - - lights.setup( lightsArray, shadowsArray, camera ); - - scene.traverse( function ( object ) { - - if ( object.material ) { - - if ( Array.isArray( object.material ) ) { - - for ( var i = 0; i < object.material.length; i ++ ) { - - initMaterial( object.material[ i ], scene.fog, object ); - - } - - } else { - - initMaterial( object.material, scene.fog, object ); - - } - - } - - } ); - - }; - - // Animation Loop - - var isAnimating = false; - var onAnimationFrame = null; - - function start() { - - if ( isAnimating ) return; - - var device = vr.getDevice(); - - if ( device && device.isPresenting ) { - - device.requestAnimationFrame( loop ); - - } else { - - window.requestAnimationFrame( loop ); - - } - - isAnimating = true; - - } - - function loop( time ) { - - if ( onAnimationFrame !== null ) onAnimationFrame( time ); - - var device = vr.getDevice(); - - if ( device && device.isPresenting ) { - - device.requestAnimationFrame( loop ); - - } else { - - window.requestAnimationFrame( loop ); - - } - - } - - this.animate = function ( callback ) { - - onAnimationFrame = callback; - start(); - - }; - - // Rendering - - this.render = function ( scene, camera, renderTarget, forceClear ) { - - if ( ! ( camera && camera.isCamera ) ) { - - console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); - return; - - } - - if ( _isContextLost ) return; - - // reset caching for this frame - - _currentGeometryProgram = ''; - _currentMaterialId = - 1; - _currentCamera = null; - - // update scene graph - - if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); - - // update camera matrices and frustum - - if ( camera.parent === null ) camera.updateMatrixWorld(); - - if ( vr.enabled ) { - - camera = vr.getCamera( camera ); - - } - - _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); - _frustum.setFromMatrix( _projScreenMatrix ); - - lightsArray.length = 0; - shadowsArray.length = 0; - - spritesArray.length = 0; - flaresArray.length = 0; - - _localClippingEnabled = this.localClippingEnabled; - _clippingEnabled = _clipping.init( this.clippingPlanes, _localClippingEnabled, camera ); - - currentRenderList = renderLists.get( scene, camera ); - currentRenderList.init(); - - projectObject( scene, camera, _this.sortObjects ); - - if ( _this.sortObjects === true ) { - - currentRenderList.sort(); - - } - - // - - if ( _clippingEnabled ) _clipping.beginShadows(); - - shadowMap.render( shadowsArray, scene, camera ); - - lights.setup( lightsArray, shadowsArray, camera ); - - if ( _clippingEnabled ) _clipping.endShadows(); - - // - - _infoRender.frame ++; - _infoRender.calls = 0; - _infoRender.vertices = 0; - _infoRender.faces = 0; - _infoRender.points = 0; - - if ( renderTarget === undefined ) { - - renderTarget = null; - - } - - this.setRenderTarget( renderTarget ); - - // - - background.render( currentRenderList, scene, camera, forceClear ); - - // render scene - - var opaqueObjects = currentRenderList.opaque; - var transparentObjects = currentRenderList.transparent; - - if ( scene.overrideMaterial ) { - - var overrideMaterial = scene.overrideMaterial; - - if ( opaqueObjects.length ) renderObjects( opaqueObjects, scene, camera, overrideMaterial ); - if ( transparentObjects.length ) renderObjects( transparentObjects, scene, camera, overrideMaterial ); - - } else { - - // opaque pass (front-to-back order) - - if ( opaqueObjects.length ) renderObjects( opaqueObjects, scene, camera ); - - // transparent pass (back-to-front order) - - if ( transparentObjects.length ) renderObjects( transparentObjects, scene, camera ); - - } - - // custom renderers - - spriteRenderer.render( spritesArray, scene, camera ); - flareRenderer.render( flaresArray, scene, camera, _currentViewport ); - - // Generate mipmap if we're using any kind of mipmap filtering - - if ( renderTarget ) { - - textures.updateRenderTargetMipmap( renderTarget ); - - } - - // Ensure depth buffer writing is enabled so it can be cleared on next render - - state.buffers.depth.setTest( true ); - state.buffers.depth.setMask( true ); - state.buffers.color.setMask( true ); - - state.setPolygonOffset( false ); - - if ( vr.enabled ) { - - vr.submitFrame(); - - } - - // _gl.finish(); - - }; - - /* - // TODO Duplicated code (Frustum) - - var _sphere = new Sphere(); - - function isObjectViewable( object ) { - - var geometry = object.geometry; - - if ( geometry.boundingSphere === null ) - geometry.computeBoundingSphere(); - - _sphere.copy( geometry.boundingSphere ). - applyMatrix4( object.matrixWorld ); - - return isSphereViewable( _sphere ); - - } - - function isSpriteViewable( sprite ) { - - _sphere.center.set( 0, 0, 0 ); - _sphere.radius = 0.7071067811865476; - _sphere.applyMatrix4( sprite.matrixWorld ); - - return isSphereViewable( _sphere ); - - } - - function isSphereViewable( sphere ) { - - if ( ! _frustum.intersectsSphere( sphere ) ) return false; - - var numPlanes = _clipping.numPlanes; - - if ( numPlanes === 0 ) return true; - - var planes = _this.clippingPlanes, - - center = sphere.center, - negRad = - sphere.radius, - i = 0; - - do { - - // out when deeper than radius in the negative halfspace - if ( planes[ i ].distanceToPoint( center ) < negRad ) return false; - - } while ( ++ i !== numPlanes ); - - return true; - - } - */ - - function projectObject( object, camera, sortObjects ) { - - if ( object.visible === false ) return; - - var visible = object.layers.test( camera.layers ); - - if ( visible ) { - - if ( object.isLight ) { - - lightsArray.push( object ); - - if ( object.castShadow ) { - - shadowsArray.push( object ); - - } - - } else if ( object.isSprite ) { - - if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { - - spritesArray.push( object ); - - } - - } else if ( object.isLensFlare ) { - - flaresArray.push( object ); - - } else if ( object.isImmediateRenderObject ) { - - if ( sortObjects ) { - - _vector3.setFromMatrixPosition( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); - - } - - currentRenderList.push( object, null, object.material, _vector3.z, null ); - - } else if ( object.isMesh || object.isLine || object.isPoints ) { - - if ( object.isSkinnedMesh ) { - - object.skeleton.update(); - - } - - if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { - - if ( sortObjects ) { - - _vector3.setFromMatrixPosition( object.matrixWorld ) - .applyMatrix4( _projScreenMatrix ); - - } - - var geometry = objects.update( object ); - var material = object.material; - - if ( Array.isArray( material ) ) { - - var groups = geometry.groups; - - for ( var i = 0, l = groups.length; i < l; i ++ ) { - - var group = groups[ i ]; - var groupMaterial = material[ group.materialIndex ]; - - if ( groupMaterial && groupMaterial.visible ) { - - currentRenderList.push( object, geometry, groupMaterial, _vector3.z, group ); - - } - - } - - } else if ( material.visible ) { - - currentRenderList.push( object, geometry, material, _vector3.z, null ); - - } - - } - - } - - } - - var children = object.children; - - for ( var i = 0, l = children.length; i < l; i ++ ) { - - projectObject( children[ i ], camera, sortObjects ); - - } - - } - - function renderObjects( renderList, scene, camera, overrideMaterial ) { - - for ( var i = 0, l = renderList.length; i < l; i ++ ) { - - var renderItem = renderList[ i ]; - - var object = renderItem.object; - var geometry = renderItem.geometry; - var material = overrideMaterial === undefined ? renderItem.material : overrideMaterial; - var group = renderItem.group; - - if ( camera.isArrayCamera ) { - - _currentArrayCamera = camera; - - var cameras = camera.cameras; - - for ( var j = 0, jl = cameras.length; j < jl; j ++ ) { - - var camera2 = cameras[ j ]; - - if ( object.layers.test( camera2.layers ) ) { - - var bounds = camera2.bounds; - - var x = bounds.x * _width; - var y = bounds.y * _height; - var width = bounds.z * _width; - var height = bounds.w * _height; - - state.viewport( _currentViewport.set( x, y, width, height ).multiplyScalar( _pixelRatio ) ); - - renderObject( object, scene, camera2, geometry, material, group ); - - } - - } - - } else { - - _currentArrayCamera = null; - - renderObject( object, scene, camera, geometry, material, group ); - - } - - } - - } - - function renderObject( object, scene, camera, geometry, material, group ) { - - object.onBeforeRender( _this, scene, camera, geometry, material, group ); - - object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); - object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); - - if ( object.isImmediateRenderObject ) { - - state.setMaterial( material ); - - var program = setProgram( camera, scene.fog, material, object ); - - _currentGeometryProgram = ''; - - renderObjectImmediate( object, program, material ); - - } else { - - _this.renderBufferDirect( camera, scene.fog, geometry, material, object, group ); - - } - - object.onAfterRender( _this, scene, camera, geometry, material, group ); - - } - - function initMaterial( material, fog, object ) { - - var materialProperties = properties.get( material ); - - var parameters = programCache.getParameters( - material, lights.state, shadowsArray, fog, _clipping.numPlanes, _clipping.numIntersection, object ); - - var code = programCache.getProgramCode( material, parameters ); - - var program = materialProperties.program; - var programChange = true; - - if ( program === undefined ) { - - // new material - material.addEventListener( 'dispose', onMaterialDispose ); - - } else if ( program.code !== code ) { - - // changed glsl or parameters - releaseMaterialProgramReference( material ); - - } else if ( parameters.shaderID !== undefined ) { - - // same glsl and uniform list - return; - - } else { - - // only rebuild uniform list - programChange = false; - - } - - if ( programChange ) { - - if ( parameters.shaderID ) { - - var shader = ShaderLib[ parameters.shaderID ]; - - materialProperties.shader = { - name: material.type, - uniforms: UniformsUtils.clone( shader.uniforms ), - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader - }; - - } else { - - materialProperties.shader = { - name: material.type, - uniforms: material.uniforms, - vertexShader: material.vertexShader, - fragmentShader: material.fragmentShader - }; - - } - - material.onBeforeCompile( materialProperties.shader ); - - program = programCache.acquireProgram( material, materialProperties.shader, parameters, code ); - - materialProperties.program = program; - material.program = program; - - } - - var programAttributes = program.getAttributes(); - - if ( material.morphTargets ) { - - material.numSupportedMorphTargets = 0; - - for ( var i = 0; i < _this.maxMorphTargets; i ++ ) { - - if ( programAttributes[ 'morphTarget' + i ] >= 0 ) { - - material.numSupportedMorphTargets ++; - - } - - } - - } - - if ( material.morphNormals ) { - - material.numSupportedMorphNormals = 0; - - for ( var i = 0; i < _this.maxMorphNormals; i ++ ) { - - if ( programAttributes[ 'morphNormal' + i ] >= 0 ) { - - material.numSupportedMorphNormals ++; - - } - - } - - } - - var uniforms = materialProperties.shader.uniforms; - - if ( ! material.isShaderMaterial && - ! material.isRawShaderMaterial || - material.clipping === true ) { - - materialProperties.numClippingPlanes = _clipping.numPlanes; - materialProperties.numIntersection = _clipping.numIntersection; - uniforms.clippingPlanes = _clipping.uniform; - - } - - materialProperties.fog = fog; - - // store the light setup it was created for - - materialProperties.lightsHash = lights.state.hash; - - if ( material.lights ) { - - // wire up the material to this renderer's lighting state - - uniforms.ambientLightColor.value = lights.state.ambient; - uniforms.directionalLights.value = lights.state.directional; - uniforms.spotLights.value = lights.state.spot; - uniforms.rectAreaLights.value = lights.state.rectArea; - uniforms.pointLights.value = lights.state.point; - uniforms.hemisphereLights.value = lights.state.hemi; - - uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; - uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; - uniforms.spotShadowMap.value = lights.state.spotShadowMap; - uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix; - uniforms.pointShadowMap.value = lights.state.pointShadowMap; - uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; - // TODO (abelnation): add area lights shadow info to uniforms - - } - - var progUniforms = materialProperties.program.getUniforms(), - uniformsList = - WebGLUniforms.seqWithValue( progUniforms.seq, uniforms ); - - materialProperties.uniformsList = uniformsList; - - } - - function setProgram( camera, fog, material, object ) { - - _usedTextureUnits = 0; - - var materialProperties = properties.get( material ); - - if ( _clippingEnabled ) { - - if ( _localClippingEnabled || camera !== _currentCamera ) { - - var useCache = - camera === _currentCamera && - material.id === _currentMaterialId; - - // we might want to call this function with some ClippingGroup - // object instead of the material, once it becomes feasible - // (#8465, #8379) - _clipping.setState( - material.clippingPlanes, material.clipIntersection, material.clipShadows, - camera, materialProperties, useCache ); - - } - - } - - if ( material.needsUpdate === false ) { - - if ( materialProperties.program === undefined ) { - - material.needsUpdate = true; - - } else if ( material.fog && materialProperties.fog !== fog ) { - - material.needsUpdate = true; - - } else if ( material.lights && materialProperties.lightsHash !== lights.state.hash ) { - - material.needsUpdate = true; - - } else if ( materialProperties.numClippingPlanes !== undefined && - ( materialProperties.numClippingPlanes !== _clipping.numPlanes || - materialProperties.numIntersection !== _clipping.numIntersection ) ) { - - material.needsUpdate = true; - - } - - } - - if ( material.needsUpdate ) { - - initMaterial( material, fog, object ); - material.needsUpdate = false; - - } - - var refreshProgram = false; - var refreshMaterial = false; - var refreshLights = false; - - var program = materialProperties.program, - p_uniforms = program.getUniforms(), - m_uniforms = materialProperties.shader.uniforms; - - if ( state.useProgram( program.program ) ) { - - refreshProgram = true; - refreshMaterial = true; - refreshLights = true; - - } - - if ( material.id !== _currentMaterialId ) { - - _currentMaterialId = material.id; - - refreshMaterial = true; - - } - - if ( refreshProgram || camera !== _currentCamera ) { - - p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); - - if ( capabilities.logarithmicDepthBuffer ) { - - p_uniforms.setValue( _gl, 'logDepthBufFC', - 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); - - } - - // Avoid unneeded uniform updates per ArrayCamera's sub-camera - - if ( _currentCamera !== ( _currentArrayCamera || camera ) ) { - - _currentCamera = ( _currentArrayCamera || camera ); - - // lighting uniforms depend on the camera so enforce an update - // now, in case this material supports lights - or later, when - // the next material that does gets activated: - - refreshMaterial = true; // set to true on material change - refreshLights = true; // remains set until update done - - } - - // load material specific uniforms - // (shader material also gets them for the sake of genericity) - - if ( material.isShaderMaterial || - material.isMeshPhongMaterial || - material.isMeshStandardMaterial || - material.envMap ) { - - var uCamPos = p_uniforms.map.cameraPosition; - - if ( uCamPos !== undefined ) { - - uCamPos.setValue( _gl, - _vector3.setFromMatrixPosition( camera.matrixWorld ) ); - - } - - } - - if ( material.isMeshPhongMaterial || - material.isMeshLambertMaterial || - material.isMeshBasicMaterial || - material.isMeshStandardMaterial || - material.isShaderMaterial || - material.skinning ) { - - p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); - - } - - } - - // skinning uniforms must be set even if material didn't change - // auto-setting of texture unit for bone texture must go before other textures - // not sure why, but otherwise weird things happen - - if ( material.skinning ) { - - p_uniforms.setOptional( _gl, object, 'bindMatrix' ); - p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); - - var skeleton = object.skeleton; - - if ( skeleton ) { - - var bones = skeleton.bones; - - if ( capabilities.floatVertexTextures ) { - - if ( skeleton.boneTexture === undefined ) { - - // layout (1 matrix = 4 pixels) - // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) - // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8) - // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16) - // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32) - // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64) - - - var size = Math.sqrt( bones.length * 4 ); // 4 pixels needed for 1 matrix - size = _Math.ceilPowerOfTwo( size ); - size = Math.max( size, 4 ); - - var boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel - boneMatrices.set( skeleton.boneMatrices ); // copy current values - - var boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType ); - - skeleton.boneMatrices = boneMatrices; - skeleton.boneTexture = boneTexture; - skeleton.boneTextureSize = size; - - } - - p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture ); - p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize ); - - } else { - - p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' ); - - } - - } - - } - - if ( refreshMaterial ) { - - p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); - p_uniforms.setValue( _gl, 'toneMappingWhitePoint', _this.toneMappingWhitePoint ); - - if ( material.lights ) { - - // the current material requires lighting info - - // note: all lighting uniforms are always set correctly - // they simply reference the renderer's state for their - // values - // - // use the current material's .needsUpdate flags to set - // the GL state when required - - markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); - - } - - // refresh uniforms common to several materials - - if ( fog && material.fog ) { - - refreshUniformsFog( m_uniforms, fog ); - - } - - if ( material.isMeshBasicMaterial ) { - - refreshUniformsCommon( m_uniforms, material ); - - } else if ( material.isMeshLambertMaterial ) { - - refreshUniformsCommon( m_uniforms, material ); - refreshUniformsLambert( m_uniforms, material ); - - } else if ( material.isMeshPhongMaterial ) { - - refreshUniformsCommon( m_uniforms, material ); - - if ( material.isMeshToonMaterial ) { - - refreshUniformsToon( m_uniforms, material ); - - } else { - - refreshUniformsPhong( m_uniforms, material ); - - } - - } else if ( material.isMeshStandardMaterial ) { - - refreshUniformsCommon( m_uniforms, material ); - - if ( material.isMeshPhysicalMaterial ) { - - refreshUniformsPhysical( m_uniforms, material ); - - } else { - - refreshUniformsStandard( m_uniforms, material ); - - } - - } else if ( material.isMeshDepthMaterial ) { - - refreshUniformsCommon( m_uniforms, material ); - refreshUniformsDepth( m_uniforms, material ); - - } else if ( material.isMeshDistanceMaterial ) { - - refreshUniformsCommon( m_uniforms, material ); - refreshUniformsDistance( m_uniforms, material ); - - } else if ( material.isMeshNormalMaterial ) { - - refreshUniformsCommon( m_uniforms, material ); - refreshUniformsNormal( m_uniforms, material ); - - } else if ( material.isLineBasicMaterial ) { - - refreshUniformsLine( m_uniforms, material ); - - if ( material.isLineDashedMaterial ) { - - refreshUniformsDash( m_uniforms, material ); - - } - - } else if ( material.isPointsMaterial ) { - - refreshUniformsPoints( m_uniforms, material ); - - } else if ( material.isShadowMaterial ) { - - m_uniforms.color.value = material.color; - m_uniforms.opacity.value = material.opacity; - - } - - // RectAreaLight Texture - // TODO (mrdoob): Find a nicer implementation - - if ( m_uniforms.ltcMat !== undefined ) m_uniforms.ltcMat.value = UniformsLib.LTC_MAT_TEXTURE; - if ( m_uniforms.ltcMag !== undefined ) m_uniforms.ltcMag.value = UniformsLib.LTC_MAG_TEXTURE; - - WebGLUniforms.upload( - _gl, materialProperties.uniformsList, m_uniforms, _this ); - - } - - - // common matrices - - p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); - p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); - p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); - - return program; - - } - - // Uniforms (refresh uniforms objects) - - function refreshUniformsCommon( uniforms, material ) { - - uniforms.opacity.value = material.opacity; - - if ( material.color ) { - - uniforms.diffuse.value = material.color; - - } - - if ( material.emissive ) { - - uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); - - } - - if ( material.map ) { - - uniforms.map.value = material.map; - - } - - if ( material.alphaMap ) { - - uniforms.alphaMap.value = material.alphaMap; - - } - - if ( material.specularMap ) { - - uniforms.specularMap.value = material.specularMap; - - } - - if ( material.envMap ) { - - uniforms.envMap.value = material.envMap; - - // don't flip CubeTexture envMaps, flip everything else: - // WebGLRenderTargetCube will be flipped for backwards compatibility - // WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture - // this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future - uniforms.flipEnvMap.value = ( ! ( material.envMap && material.envMap.isCubeTexture ) ) ? 1 : - 1; - - uniforms.reflectivity.value = material.reflectivity; - uniforms.refractionRatio.value = material.refractionRatio; - - } - - if ( material.lightMap ) { - - uniforms.lightMap.value = material.lightMap; - uniforms.lightMapIntensity.value = material.lightMapIntensity; - - } - - if ( material.aoMap ) { - - uniforms.aoMap.value = material.aoMap; - uniforms.aoMapIntensity.value = material.aoMapIntensity; - - } - - // uv repeat and offset setting priorities - // 1. color map - // 2. specular map - // 3. normal map - // 4. bump map - // 5. alpha map - // 6. emissive map - - var uvScaleMap; - - if ( material.map ) { - - uvScaleMap = material.map; - - } else if ( material.specularMap ) { - - uvScaleMap = material.specularMap; - - } else if ( material.displacementMap ) { - - uvScaleMap = material.displacementMap; - - } else if ( material.normalMap ) { - - uvScaleMap = material.normalMap; - - } else if ( material.bumpMap ) { - - uvScaleMap = material.bumpMap; - - } else if ( material.roughnessMap ) { - - uvScaleMap = material.roughnessMap; - - } else if ( material.metalnessMap ) { - - uvScaleMap = material.metalnessMap; - - } else if ( material.alphaMap ) { - - uvScaleMap = material.alphaMap; - - } else if ( material.emissiveMap ) { - - uvScaleMap = material.emissiveMap; - - } - - if ( uvScaleMap !== undefined ) { - - // backwards compatibility - if ( uvScaleMap.isWebGLRenderTarget ) { - - uvScaleMap = uvScaleMap.texture; - - } - - if ( uvScaleMap.matrixAutoUpdate === true ) { - - var offset = uvScaleMap.offset; - var repeat = uvScaleMap.repeat; - var rotation = uvScaleMap.rotation; - var center = uvScaleMap.center; - - uvScaleMap.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, center.x, center.y ); - - } - - uniforms.uvTransform.value.copy( uvScaleMap.matrix ); - - } - - } - - function refreshUniformsLine( uniforms, material ) { - - uniforms.diffuse.value = material.color; - uniforms.opacity.value = material.opacity; - - } - - function refreshUniformsDash( uniforms, material ) { - - uniforms.dashSize.value = material.dashSize; - uniforms.totalSize.value = material.dashSize + material.gapSize; - uniforms.scale.value = material.scale; - - } - - function refreshUniformsPoints( uniforms, material ) { - - uniforms.diffuse.value = material.color; - uniforms.opacity.value = material.opacity; - uniforms.size.value = material.size * _pixelRatio; - uniforms.scale.value = _height * 0.5; - - uniforms.map.value = material.map; - - if ( material.map !== null ) { - - if ( material.map.matrixAutoUpdate === true ) { - - var offset = material.map.offset; - var repeat = material.map.repeat; - var rotation = material.map.rotation; - var center = material.map.center; - - material.map.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, center.x, center.y ); - - } - - uniforms.uvTransform.value.copy( material.map.matrix ); - - } - - } - - function refreshUniformsFog( uniforms, fog ) { - - uniforms.fogColor.value = fog.color; - - if ( fog.isFog ) { - - uniforms.fogNear.value = fog.near; - uniforms.fogFar.value = fog.far; - - } else if ( fog.isFogExp2 ) { - - uniforms.fogDensity.value = fog.density; - - } - - } - - function refreshUniformsLambert( uniforms, material ) { - - if ( material.emissiveMap ) { - - uniforms.emissiveMap.value = material.emissiveMap; - - } - - } - - function refreshUniformsPhong( uniforms, material ) { - - uniforms.specular.value = material.specular; - uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) - - if ( material.emissiveMap ) { - - uniforms.emissiveMap.value = material.emissiveMap; - - } - - if ( material.bumpMap ) { - - uniforms.bumpMap.value = material.bumpMap; - uniforms.bumpScale.value = material.bumpScale; - - } - - if ( material.normalMap ) { - - uniforms.normalMap.value = material.normalMap; - uniforms.normalScale.value.copy( material.normalScale ); - - } - - if ( material.displacementMap ) { - - uniforms.displacementMap.value = material.displacementMap; - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; - - } - - } - - function refreshUniformsToon( uniforms, material ) { - - refreshUniformsPhong( uniforms, material ); - - if ( material.gradientMap ) { - - uniforms.gradientMap.value = material.gradientMap; - - } - - } - - function refreshUniformsStandard( uniforms, material ) { - - uniforms.roughness.value = material.roughness; - uniforms.metalness.value = material.metalness; - - if ( material.roughnessMap ) { - - uniforms.roughnessMap.value = material.roughnessMap; - - } - - if ( material.metalnessMap ) { - - uniforms.metalnessMap.value = material.metalnessMap; - - } - - if ( material.emissiveMap ) { - - uniforms.emissiveMap.value = material.emissiveMap; - - } - - if ( material.bumpMap ) { - - uniforms.bumpMap.value = material.bumpMap; - uniforms.bumpScale.value = material.bumpScale; - - } - - if ( material.normalMap ) { - - uniforms.normalMap.value = material.normalMap; - uniforms.normalScale.value.copy( material.normalScale ); - - } - - if ( material.displacementMap ) { - - uniforms.displacementMap.value = material.displacementMap; - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; - - } - - if ( material.envMap ) { - - //uniforms.envMap.value = material.envMap; // part of uniforms common - uniforms.envMapIntensity.value = material.envMapIntensity; - - } - - } - - function refreshUniformsPhysical( uniforms, material ) { - - uniforms.clearCoat.value = material.clearCoat; - uniforms.clearCoatRoughness.value = material.clearCoatRoughness; - - refreshUniformsStandard( uniforms, material ); - - } - - function refreshUniformsDepth( uniforms, material ) { - - if ( material.displacementMap ) { - - uniforms.displacementMap.value = material.displacementMap; - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; - - } - - } - - function refreshUniformsDistance( uniforms, material ) { - - if ( material.displacementMap ) { - - uniforms.displacementMap.value = material.displacementMap; - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; - - } - - uniforms.referencePosition.value.copy( material.referencePosition ); - uniforms.nearDistance.value = material.nearDistance; - uniforms.farDistance.value = material.farDistance; - - } - - function refreshUniformsNormal( uniforms, material ) { - - if ( material.bumpMap ) { - - uniforms.bumpMap.value = material.bumpMap; - uniforms.bumpScale.value = material.bumpScale; - - } - - if ( material.normalMap ) { - - uniforms.normalMap.value = material.normalMap; - uniforms.normalScale.value.copy( material.normalScale ); - - } - - if ( material.displacementMap ) { - - uniforms.displacementMap.value = material.displacementMap; - uniforms.displacementScale.value = material.displacementScale; - uniforms.displacementBias.value = material.displacementBias; - - } - - } - - // If uniforms are marked as clean, they don't need to be loaded to the GPU. - - function markUniformsLightsNeedsUpdate( uniforms, value ) { - - uniforms.ambientLightColor.needsUpdate = value; - - uniforms.directionalLights.needsUpdate = value; - uniforms.pointLights.needsUpdate = value; - uniforms.spotLights.needsUpdate = value; - uniforms.rectAreaLights.needsUpdate = value; - uniforms.hemisphereLights.needsUpdate = value; - - } - - // GL state setting - - this.setFaceCulling = function ( cullFace, frontFaceDirection ) { - - state.setCullFace( cullFace ); - state.setFlipSided( frontFaceDirection === FrontFaceDirectionCW ); - - }; - - // Textures - - function allocTextureUnit() { - - var textureUnit = _usedTextureUnits; - - if ( textureUnit >= capabilities.maxTextures ) { - - console.warn( 'THREE.WebGLRenderer: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures ); - - } - - _usedTextureUnits += 1; - - return textureUnit; - - } - - this.allocTextureUnit = allocTextureUnit; - - // this.setTexture2D = setTexture2D; - this.setTexture2D = ( function () { - - var warned = false; - - // backwards compatibility: peel texture.texture - return function setTexture2D( texture, slot ) { - - if ( texture && texture.isWebGLRenderTarget ) { - - if ( ! warned ) { - - console.warn( "THREE.WebGLRenderer.setTexture2D: don't use render targets as textures. Use their .texture property instead." ); - warned = true; - - } - - texture = texture.texture; - - } - - textures.setTexture2D( texture, slot ); - - }; - - }() ); - - this.setTexture = ( function () { - - var warned = false; - - return function setTexture( texture, slot ) { - - if ( ! warned ) { - - console.warn( "THREE.WebGLRenderer: .setTexture is deprecated, use setTexture2D instead." ); - warned = true; - - } - - textures.setTexture2D( texture, slot ); - - }; - - }() ); - - this.setTextureCube = ( function () { - - var warned = false; - - return function setTextureCube( texture, slot ) { - - // backwards compatibility: peel texture.texture - if ( texture && texture.isWebGLRenderTargetCube ) { - - if ( ! warned ) { - - console.warn( "THREE.WebGLRenderer.setTextureCube: don't use cube render targets as textures. Use their .texture property instead." ); - warned = true; - - } - - texture = texture.texture; - - } - - // currently relying on the fact that WebGLRenderTargetCube.texture is a Texture and NOT a CubeTexture - // TODO: unify these code paths - if ( ( texture && texture.isCubeTexture ) || - ( Array.isArray( texture.image ) && texture.image.length === 6 ) ) { - - // CompressedTexture can have Array in image :/ - - // this function alone should take care of cube textures - textures.setTextureCube( texture, slot ); - - } else { - - // assumed: texture property of THREE.WebGLRenderTargetCube - - textures.setTextureCubeDynamic( texture, slot ); - - } - - }; - - }() ); - - this.getRenderTarget = function () { - - return _currentRenderTarget; - - }; - - this.setRenderTarget = function ( renderTarget ) { - - _currentRenderTarget = renderTarget; - - if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) { - - textures.setupRenderTarget( renderTarget ); - - } - - var framebuffer = null; - var isCube = false; - - if ( renderTarget ) { - - var __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; - - if ( renderTarget.isWebGLRenderTargetCube ) { - - framebuffer = __webglFramebuffer[ renderTarget.activeCubeFace ]; - isCube = true; - - } else { - - framebuffer = __webglFramebuffer; - - } - - _currentViewport.copy( renderTarget.viewport ); - _currentScissor.copy( renderTarget.scissor ); - _currentScissorTest = renderTarget.scissorTest; - - } else { - - _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ); - _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ); - _currentScissorTest = _scissorTest; - - } - - if ( _currentFramebuffer !== framebuffer ) { - - _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - _currentFramebuffer = framebuffer; - - } - - state.viewport( _currentViewport ); - state.scissor( _currentScissor ); - state.setScissorTest( _currentScissorTest ); - - if ( isCube ) { - - var textureProperties = properties.get( renderTarget.texture ); - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderTarget.activeCubeFace, textureProperties.__webglTexture, renderTarget.activeMipMapLevel ); - - } - - }; - - this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer ) { - - if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); - return; - - } - - var framebuffer = properties.get( renderTarget ).__webglFramebuffer; - - if ( framebuffer ) { - - var restore = false; - - if ( framebuffer !== _currentFramebuffer ) { - - _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); - - restore = true; - - } - - try { - - var texture = renderTarget.texture; - var textureFormat = texture.format; - var textureType = texture.type; - - if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); - return; - - } - - if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // IE11, Edge and Chrome Mac < 52 (#9513) - ! ( textureType === FloatType && ( extensions.get( 'OES_texture_float' ) || extensions.get( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox - ! ( textureType === HalfFloatType && extensions.get( 'EXT_color_buffer_half_float' ) ) ) { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); - return; - - } - - if ( _gl.checkFramebufferStatus( _gl.FRAMEBUFFER ) === _gl.FRAMEBUFFER_COMPLETE ) { - - // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) - - if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { - - _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); - - } - - } else { - - console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' ); - - } - - } finally { - - if ( restore ) { - - _gl.bindFramebuffer( _gl.FRAMEBUFFER, _currentFramebuffer ); - - } - - } - - } - - }; - -} - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ - -function FogExp2( color, density ) { - - this.name = ''; - - this.color = new Color( color ); - this.density = ( density !== undefined ) ? density : 0.00025; - -} - -FogExp2.prototype.isFogExp2 = true; - -FogExp2.prototype.clone = function () { - - return new FogExp2( this.color.getHex(), this.density ); - -}; - -FogExp2.prototype.toJSON = function ( /* meta */ ) { - - return { - type: 'FogExp2', - color: this.color.getHex(), - density: this.density - }; - -}; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ - -function Fog( color, near, far ) { - - this.name = ''; - - this.color = new Color( color ); - - this.near = ( near !== undefined ) ? near : 1; - this.far = ( far !== undefined ) ? far : 1000; - -} - -Fog.prototype.isFog = true; - -Fog.prototype.clone = function () { - - return new Fog( this.color.getHex(), this.near, this.far ); - -}; - -Fog.prototype.toJSON = function ( /* meta */ ) { - - return { - type: 'Fog', - color: this.color.getHex(), - near: this.near, - far: this.far - }; - -}; - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function Scene() { - - Object3D.call( this ); - - this.type = 'Scene'; - - this.background = null; - this.fog = null; - this.overrideMaterial = null; - - this.autoUpdate = true; // checked by the renderer - -} - -Scene.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: Scene, - - copy: function ( source, recursive ) { - - Object3D.prototype.copy.call( this, source, recursive ); - - if ( source.background !== null ) this.background = source.background.clone(); - if ( source.fog !== null ) this.fog = source.fog.clone(); - if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); - - this.autoUpdate = source.autoUpdate; - this.matrixAutoUpdate = source.matrixAutoUpdate; - - return this; - - }, - - toJSON: function ( meta ) { - - var data = Object3D.prototype.toJSON.call( this, meta ); - - if ( this.background !== null ) data.object.background = this.background.toJSON( meta ); - if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); - - return data; - - } - -} ); - -/** - * @author mikael emtinger / http://gomo.se/ - * @author alteredq / http://alteredqualia.com/ - */ - -function LensFlare( texture, size, distance, blending, color ) { - - Object3D.call( this ); - - this.lensFlares = []; - - this.positionScreen = new Vector3(); - this.customUpdateCallback = undefined; - - if ( texture !== undefined ) { - - this.add( texture, size, distance, blending, color ); - - } - -} - -LensFlare.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: LensFlare, - - isLensFlare: true, - - copy: function ( source ) { - - Object3D.prototype.copy.call( this, source ); - - this.positionScreen.copy( source.positionScreen ); - this.customUpdateCallback = source.customUpdateCallback; - - for ( var i = 0, l = source.lensFlares.length; i < l; i ++ ) { - - this.lensFlares.push( source.lensFlares[ i ] ); - - } - - return this; - - }, - - add: function ( texture, size, distance, blending, color, opacity ) { - - if ( size === undefined ) size = - 1; - if ( distance === undefined ) distance = 0; - if ( opacity === undefined ) opacity = 1; - if ( color === undefined ) color = new Color( 0xffffff ); - if ( blending === undefined ) blending = NormalBlending; - - distance = Math.min( distance, Math.max( 0, distance ) ); - - this.lensFlares.push( { - texture: texture, // THREE.Texture - size: size, // size in pixels (-1 = use texture.width) - distance: distance, // distance (0-1) from light source (0=at light source) - x: 0, y: 0, z: 0, // screen position (-1 => 1) z = 0 is in front z = 1 is back - scale: 1, // scale - rotation: 0, // rotation - opacity: opacity, // opacity - color: color, // color - blending: blending // blending - } ); - - }, - - /* - * Update lens flares update positions on all flares based on the screen position - * Set myLensFlare.customUpdateCallback to alter the flares in your project specific way. - */ - - updateLensFlares: function () { - - var f, fl = this.lensFlares.length; - var flare; - var vecX = - this.positionScreen.x * 2; - var vecY = - this.positionScreen.y * 2; - - for ( f = 0; f < fl; f ++ ) { - - flare = this.lensFlares[ f ]; - - flare.x = this.positionScreen.x + vecX * flare.distance; - flare.y = this.positionScreen.y + vecY * flare.distance; - - flare.wantedRotation = flare.x * Math.PI * 0.25; - flare.rotation += ( flare.wantedRotation - flare.rotation ) * 0.25; - - } - - } - -} ); - -/** - * @author alteredq / http://alteredqualia.com/ - * - * parameters = { - * color: , - * opacity: , - * map: new THREE.Texture( ), - * - * uvOffset: new THREE.Vector2(), - * uvScale: new THREE.Vector2() - * } - */ - -function SpriteMaterial( parameters ) { - - Material.call( this ); - - this.type = 'SpriteMaterial'; - - this.color = new Color( 0xffffff ); - this.map = null; - - this.rotation = 0; - - this.fog = false; - this.lights = false; - - this.setValues( parameters ); - -} - -SpriteMaterial.prototype = Object.create( Material.prototype ); -SpriteMaterial.prototype.constructor = SpriteMaterial; -SpriteMaterial.prototype.isSpriteMaterial = true; - -SpriteMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.color.copy( source.color ); - this.map = source.map; - - this.rotation = source.rotation; - - return this; - -}; - -/** - * @author mikael emtinger / http://gomo.se/ - * @author alteredq / http://alteredqualia.com/ - */ - -function Sprite( material ) { - - Object3D.call( this ); - - this.type = 'Sprite'; - - this.material = ( material !== undefined ) ? material : new SpriteMaterial(); - -} - -Sprite.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: Sprite, - - isSprite: true, - - raycast: ( function () { - - var intersectPoint = new Vector3(); - var worldPosition = new Vector3(); - var worldScale = new Vector3(); - - return function raycast( raycaster, intersects ) { - - worldPosition.setFromMatrixPosition( this.matrixWorld ); - raycaster.ray.closestPointToPoint( worldPosition, intersectPoint ); - - worldScale.setFromMatrixScale( this.matrixWorld ); - var guessSizeSq = worldScale.x * worldScale.y / 4; - - if ( worldPosition.distanceToSquared( intersectPoint ) > guessSizeSq ) return; - - var distance = raycaster.ray.origin.distanceTo( intersectPoint ); - - if ( distance < raycaster.near || distance > raycaster.far ) return; - - intersects.push( { - - distance: distance, - point: intersectPoint.clone(), - face: null, - object: this - - } ); - - }; - - }() ), - - clone: function () { - - return new this.constructor( this.material ).copy( this ); - - } - -} ); - -/** - * @author mikael emtinger / http://gomo.se/ - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - */ - -function LOD() { - - Object3D.call( this ); - - this.type = 'LOD'; - - Object.defineProperties( this, { - levels: { - enumerable: true, - value: [] - } - } ); - -} - -LOD.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: LOD, - - copy: function ( source ) { - - Object3D.prototype.copy.call( this, source, false ); - - var levels = source.levels; - - for ( var i = 0, l = levels.length; i < l; i ++ ) { - - var level = levels[ i ]; - - this.addLevel( level.object.clone(), level.distance ); - - } - - return this; - - }, - - addLevel: function ( object, distance ) { - - if ( distance === undefined ) distance = 0; - - distance = Math.abs( distance ); - - var levels = this.levels; - - for ( var l = 0; l < levels.length; l ++ ) { - - if ( distance < levels[ l ].distance ) { - - break; - - } - - } - - levels.splice( l, 0, { distance: distance, object: object } ); - - this.add( object ); - - }, - - getObjectForDistance: function ( distance ) { - - var levels = this.levels; - - for ( var i = 1, l = levels.length; i < l; i ++ ) { - - if ( distance < levels[ i ].distance ) { - - break; - - } - - } - - return levels[ i - 1 ].object; - - }, - - raycast: ( function () { - - var matrixPosition = new Vector3(); - - return function raycast( raycaster, intersects ) { - - matrixPosition.setFromMatrixPosition( this.matrixWorld ); - - var distance = raycaster.ray.origin.distanceTo( matrixPosition ); - - this.getObjectForDistance( distance ).raycast( raycaster, intersects ); - - }; - - }() ), - - update: function () { - - var v1 = new Vector3(); - var v2 = new Vector3(); - - return function update( camera ) { - - var levels = this.levels; - - if ( levels.length > 1 ) { - - v1.setFromMatrixPosition( camera.matrixWorld ); - v2.setFromMatrixPosition( this.matrixWorld ); - - var distance = v1.distanceTo( v2 ); - - levels[ 0 ].object.visible = true; - - for ( var i = 1, l = levels.length; i < l; i ++ ) { - - if ( distance >= levels[ i ].distance ) { - - levels[ i - 1 ].object.visible = false; - levels[ i ].object.visible = true; - - } else { - - break; - - } - - } - - for ( ; i < l; i ++ ) { - - levels[ i ].object.visible = false; - - } - - } - - }; - - }(), - - toJSON: function ( meta ) { - - var data = Object3D.prototype.toJSON.call( this, meta ); - - data.object.levels = []; - - var levels = this.levels; - - for ( var i = 0, l = levels.length; i < l; i ++ ) { - - var level = levels[ i ]; - - data.object.levels.push( { - object: level.object.uuid, - distance: level.distance - } ); - - } - - return data; - - } - -} ); - -/** - * @author mikael emtinger / http://gomo.se/ - * @author alteredq / http://alteredqualia.com/ - * @author michael guerrero / http://realitymeltdown.com - * @author ikerr / http://verold.com - */ - -function Skeleton( bones, boneInverses ) { - - // copy the bone array - - bones = bones || []; - - this.bones = bones.slice( 0 ); - this.boneMatrices = new Float32Array( this.bones.length * 16 ); - - // use the supplied bone inverses or calculate the inverses - - if ( boneInverses === undefined ) { - - this.calculateInverses(); - - } else { - - if ( this.bones.length === boneInverses.length ) { - - this.boneInverses = boneInverses.slice( 0 ); - - } else { - - console.warn( 'THREE.Skeleton boneInverses is the wrong length.' ); - - this.boneInverses = []; - - for ( var i = 0, il = this.bones.length; i < il; i ++ ) { - - this.boneInverses.push( new Matrix4() ); - - } - - } - - } - -} - -Object.assign( Skeleton.prototype, { - - calculateInverses: function () { - - this.boneInverses = []; - - for ( var i = 0, il = this.bones.length; i < il; i ++ ) { - - var inverse = new Matrix4(); - - if ( this.bones[ i ] ) { - - inverse.getInverse( this.bones[ i ].matrixWorld ); - - } - - this.boneInverses.push( inverse ); - - } - - }, - - pose: function () { - - var bone, i, il; - - // recover the bind-time world matrices - - for ( i = 0, il = this.bones.length; i < il; i ++ ) { - - bone = this.bones[ i ]; - - if ( bone ) { - - bone.matrixWorld.getInverse( this.boneInverses[ i ] ); - - } - - } - - // compute the local matrices, positions, rotations and scales - - for ( i = 0, il = this.bones.length; i < il; i ++ ) { - - bone = this.bones[ i ]; - - if ( bone ) { - - if ( bone.parent && bone.parent.isBone ) { - - bone.matrix.getInverse( bone.parent.matrixWorld ); - bone.matrix.multiply( bone.matrixWorld ); - - } else { - - bone.matrix.copy( bone.matrixWorld ); - - } - - bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); - - } - - } - - }, - - update: ( function () { - - var offsetMatrix = new Matrix4(); - var identityMatrix = new Matrix4(); - - return function update() { - - var bones = this.bones; - var boneInverses = this.boneInverses; - var boneMatrices = this.boneMatrices; - var boneTexture = this.boneTexture; - - // flatten bone matrices to array - - for ( var i = 0, il = bones.length; i < il; i ++ ) { - - // compute the offset between the current and the original transform - - var matrix = bones[ i ] ? bones[ i ].matrixWorld : identityMatrix; - - offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] ); - offsetMatrix.toArray( boneMatrices, i * 16 ); - - } - - if ( boneTexture !== undefined ) { - - boneTexture.needsUpdate = true; - - } - - }; - - } )(), - - clone: function () { - - return new Skeleton( this.bones, this.boneInverses ); - - } - -} ); - -/** - * @author mikael emtinger / http://gomo.se/ - * @author alteredq / http://alteredqualia.com/ - * @author ikerr / http://verold.com - */ - -function Bone() { - - Object3D.call( this ); - - this.type = 'Bone'; - -} - -Bone.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: Bone, - - isBone: true - -} ); - -/** - * @author mikael emtinger / http://gomo.se/ - * @author alteredq / http://alteredqualia.com/ - * @author ikerr / http://verold.com - */ - -function SkinnedMesh( geometry, material ) { - - Mesh.call( this, geometry, material ); - - this.type = 'SkinnedMesh'; - - this.bindMode = 'attached'; - this.bindMatrix = new Matrix4(); - this.bindMatrixInverse = new Matrix4(); - - var bones = this.initBones(); - var skeleton = new Skeleton( bones ); - - this.bind( skeleton, this.matrixWorld ); - - this.normalizeSkinWeights(); - -} - -SkinnedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), { - - constructor: SkinnedMesh, - - isSkinnedMesh: true, - - initBones: function () { - - var bones = [], bone, gbone; - var i, il; - - if ( this.geometry && this.geometry.bones !== undefined ) { - - // first, create array of 'Bone' objects from geometry data - - for ( i = 0, il = this.geometry.bones.length; i < il; i ++ ) { - - gbone = this.geometry.bones[ i ]; - - // create new 'Bone' object - - bone = new Bone(); - bones.push( bone ); - - // apply values - - bone.name = gbone.name; - bone.position.fromArray( gbone.pos ); - bone.quaternion.fromArray( gbone.rotq ); - if ( gbone.scl !== undefined ) bone.scale.fromArray( gbone.scl ); - - } - - // second, create bone hierarchy - - for ( i = 0, il = this.geometry.bones.length; i < il; i ++ ) { - - gbone = this.geometry.bones[ i ]; - - if ( ( gbone.parent !== - 1 ) && ( gbone.parent !== null ) && ( bones[ gbone.parent ] !== undefined ) ) { - - // subsequent bones in the hierarchy - - bones[ gbone.parent ].add( bones[ i ] ); - - } else { - - // topmost bone, immediate child of the skinned mesh - - this.add( bones[ i ] ); - - } - - } - - } - - // now the bones are part of the scene graph and children of the skinned mesh. - // let's update the corresponding matrices - - this.updateMatrixWorld( true ); - - return bones; - - }, - - bind: function ( skeleton, bindMatrix ) { - - this.skeleton = skeleton; - - if ( bindMatrix === undefined ) { - - this.updateMatrixWorld( true ); - - this.skeleton.calculateInverses(); - - bindMatrix = this.matrixWorld; - - } - - this.bindMatrix.copy( bindMatrix ); - this.bindMatrixInverse.getInverse( bindMatrix ); - - }, - - pose: function () { - - this.skeleton.pose(); - - }, - - normalizeSkinWeights: function () { - - var scale, i; - - if ( this.geometry && this.geometry.isGeometry ) { - - for ( i = 0; i < this.geometry.skinWeights.length; i ++ ) { - - var sw = this.geometry.skinWeights[ i ]; - - scale = 1.0 / sw.manhattanLength(); - - if ( scale !== Infinity ) { - - sw.multiplyScalar( scale ); - - } else { - - sw.set( 1, 0, 0, 0 ); // do something reasonable - - } - - } - - } else if ( this.geometry && this.geometry.isBufferGeometry ) { - - var vec = new Vector4(); - - var skinWeight = this.geometry.attributes.skinWeight; - - for ( i = 0; i < skinWeight.count; i ++ ) { - - vec.x = skinWeight.getX( i ); - vec.y = skinWeight.getY( i ); - vec.z = skinWeight.getZ( i ); - vec.w = skinWeight.getW( i ); - - scale = 1.0 / vec.manhattanLength(); - - if ( scale !== Infinity ) { - - vec.multiplyScalar( scale ); - - } else { - - vec.set( 1, 0, 0, 0 ); // do something reasonable - - } - - skinWeight.setXYZW( i, vec.x, vec.y, vec.z, vec.w ); - - } - - } - - }, - - updateMatrixWorld: function ( force ) { - - Mesh.prototype.updateMatrixWorld.call( this, force ); - - if ( this.bindMode === 'attached' ) { - - this.bindMatrixInverse.getInverse( this.matrixWorld ); - - } else if ( this.bindMode === 'detached' ) { - - this.bindMatrixInverse.getInverse( this.bindMatrix ); - - } else { - - console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode ); - - } - - }, - - clone: function () { - - return new this.constructor( this.geometry, this.material ).copy( this ); - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * - * parameters = { - * color: , - * opacity: , - * - * linewidth: , - * linecap: "round", - * linejoin: "round" - * } - */ - -function LineBasicMaterial( parameters ) { - - Material.call( this ); - - this.type = 'LineBasicMaterial'; - - this.color = new Color( 0xffffff ); - - this.linewidth = 1; - this.linecap = 'round'; - this.linejoin = 'round'; - - this.lights = false; - - this.setValues( parameters ); - -} - -LineBasicMaterial.prototype = Object.create( Material.prototype ); -LineBasicMaterial.prototype.constructor = LineBasicMaterial; - -LineBasicMaterial.prototype.isLineBasicMaterial = true; - -LineBasicMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.color.copy( source.color ); - - this.linewidth = source.linewidth; - this.linecap = source.linecap; - this.linejoin = source.linejoin; - - return this; - -}; - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function Line( geometry, material, mode ) { - - if ( mode === 1 ) { - - console.warn( 'THREE.Line: parameter THREE.LinePieces no longer supported. Created THREE.LineSegments instead.' ); - return new LineSegments( geometry, material ); - - } - - Object3D.call( this ); - - this.type = 'Line'; - - this.geometry = geometry !== undefined ? geometry : new BufferGeometry(); - this.material = material !== undefined ? material : new LineBasicMaterial( { color: Math.random() * 0xffffff } ); - -} - -Line.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: Line, - - isLine: true, - - raycast: ( function () { - - var inverseMatrix = new Matrix4(); - var ray = new Ray(); - var sphere = new Sphere(); - - return function raycast( raycaster, intersects ) { - - var precision = raycaster.linePrecision; - var precisionSq = precision * precision; - - var geometry = this.geometry; - var matrixWorld = this.matrixWorld; - - // Checking boundingSphere distance to ray - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - sphere.copy( geometry.boundingSphere ); - sphere.applyMatrix4( matrixWorld ); - - if ( raycaster.ray.intersectsSphere( sphere ) === false ) return; - - // - - inverseMatrix.getInverse( matrixWorld ); - ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); - - var vStart = new Vector3(); - var vEnd = new Vector3(); - var interSegment = new Vector3(); - var interRay = new Vector3(); - var step = ( this && this.isLineSegments ) ? 2 : 1; - - if ( geometry.isBufferGeometry ) { - - var index = geometry.index; - var attributes = geometry.attributes; - var positions = attributes.position.array; - - if ( index !== null ) { - - var indices = index.array; - - for ( var i = 0, l = indices.length - 1; i < l; i += step ) { - - var a = indices[ i ]; - var b = indices[ i + 1 ]; - - vStart.fromArray( positions, a * 3 ); - vEnd.fromArray( positions, b * 3 ); - - var distSq = ray.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); - - if ( distSq > precisionSq ) continue; - - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - - var distance = raycaster.ray.origin.distanceTo( interRay ); - - if ( distance < raycaster.near || distance > raycaster.far ) continue; - - intersects.push( { - - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this - - } ); - - } - - } else { - - for ( var i = 0, l = positions.length / 3 - 1; i < l; i += step ) { - - vStart.fromArray( positions, 3 * i ); - vEnd.fromArray( positions, 3 * i + 3 ); - - var distSq = ray.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); - - if ( distSq > precisionSq ) continue; - - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - - var distance = raycaster.ray.origin.distanceTo( interRay ); - - if ( distance < raycaster.near || distance > raycaster.far ) continue; - - intersects.push( { - - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this - - } ); - - } - - } - - } else if ( geometry.isGeometry ) { - - var vertices = geometry.vertices; - var nbVertices = vertices.length; - - for ( var i = 0; i < nbVertices - 1; i += step ) { - - var distSq = ray.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment ); - - if ( distSq > precisionSq ) continue; - - interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation - - var distance = raycaster.ray.origin.distanceTo( interRay ); - - if ( distance < raycaster.near || distance > raycaster.far ) continue; - - intersects.push( { - - distance: distance, - // What do we want? intersection point on the ray or on the segment?? - // point: raycaster.ray.at( distance ), - point: interSegment.clone().applyMatrix4( this.matrixWorld ), - index: i, - face: null, - faceIndex: null, - object: this - - } ); - - } - - } - - }; - - }() ), - - clone: function () { - - return new this.constructor( this.geometry, this.material ).copy( this ); - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function LineSegments( geometry, material ) { - - Line.call( this, geometry, material ); - - this.type = 'LineSegments'; - -} - -LineSegments.prototype = Object.assign( Object.create( Line.prototype ), { - - constructor: LineSegments, - - isLineSegments: true - -} ); - -/** - * @author mgreter / http://github.com/mgreter - */ - -function LineLoop( geometry, material ) { - - Line.call( this, geometry, material ); - - this.type = 'LineLoop'; - -} - -LineLoop.prototype = Object.assign( Object.create( Line.prototype ), { - - constructor: LineLoop, - - isLineLoop: true, - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * - * parameters = { - * color: , - * opacity: , - * map: new THREE.Texture( ), - * - * size: , - * sizeAttenuation: - * } - */ - -function PointsMaterial( parameters ) { - - Material.call( this ); - - this.type = 'PointsMaterial'; - - this.color = new Color( 0xffffff ); - - this.map = null; - - this.size = 1; - this.sizeAttenuation = true; - - this.lights = false; - - this.setValues( parameters ); - -} - -PointsMaterial.prototype = Object.create( Material.prototype ); -PointsMaterial.prototype.constructor = PointsMaterial; - -PointsMaterial.prototype.isPointsMaterial = true; - -PointsMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.size = source.size; - this.sizeAttenuation = source.sizeAttenuation; - - return this; - -}; - -/** - * @author alteredq / http://alteredqualia.com/ - */ - -function Points( geometry, material ) { - - Object3D.call( this ); - - this.type = 'Points'; - - this.geometry = geometry !== undefined ? geometry : new BufferGeometry(); - this.material = material !== undefined ? material : new PointsMaterial( { color: Math.random() * 0xffffff } ); - -} - -Points.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: Points, - - isPoints: true, - - raycast: ( function () { - - var inverseMatrix = new Matrix4(); - var ray = new Ray(); - var sphere = new Sphere(); - - return function raycast( raycaster, intersects ) { - - var object = this; - var geometry = this.geometry; - var matrixWorld = this.matrixWorld; - var threshold = raycaster.params.Points.threshold; - - // Checking boundingSphere distance to ray - - if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); - - sphere.copy( geometry.boundingSphere ); - sphere.applyMatrix4( matrixWorld ); - sphere.radius += threshold; - - if ( raycaster.ray.intersectsSphere( sphere ) === false ) return; - - // - - inverseMatrix.getInverse( matrixWorld ); - ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); - - var localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); - var localThresholdSq = localThreshold * localThreshold; - var position = new Vector3(); - - function testPoint( point, index ) { - - var rayPointDistanceSq = ray.distanceSqToPoint( point ); - - if ( rayPointDistanceSq < localThresholdSq ) { - - var intersectPoint = ray.closestPointToPoint( point ); - intersectPoint.applyMatrix4( matrixWorld ); - - var distance = raycaster.ray.origin.distanceTo( intersectPoint ); - - if ( distance < raycaster.near || distance > raycaster.far ) return; - - intersects.push( { - - distance: distance, - distanceToRay: Math.sqrt( rayPointDistanceSq ), - point: intersectPoint.clone(), - index: index, - face: null, - object: object - - } ); - - } - - } - - if ( geometry.isBufferGeometry ) { - - var index = geometry.index; - var attributes = geometry.attributes; - var positions = attributes.position.array; - - if ( index !== null ) { - - var indices = index.array; - - for ( var i = 0, il = indices.length; i < il; i ++ ) { - - var a = indices[ i ]; - - position.fromArray( positions, a * 3 ); - - testPoint( position, a ); - - } - - } else { - - for ( var i = 0, l = positions.length / 3; i < l; i ++ ) { - - position.fromArray( positions, i * 3 ); - - testPoint( position, i ); - - } - - } - - } else { - - var vertices = geometry.vertices; - - for ( var i = 0, l = vertices.length; i < l; i ++ ) { - - testPoint( vertices[ i ], i ); - - } - - } - - }; - - }() ), - - clone: function () { - - return new this.constructor( this.geometry, this.material ).copy( this ); - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function Group() { - - Object3D.call( this ); - - this.type = 'Group'; - -} - -Group.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: Group - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function VideoTexture( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { - - Texture.call( this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - this.generateMipmaps = false; - - var scope = this; - - function update() { - - var video = scope.image; - - if ( video.readyState >= video.HAVE_CURRENT_DATA ) { - - scope.needsUpdate = true; - - } - - requestAnimationFrame( update ); - - } - - requestAnimationFrame( update ); - -} - -VideoTexture.prototype = Object.create( Texture.prototype ); -VideoTexture.prototype.constructor = VideoTexture; - -/** - * @author alteredq / http://alteredqualia.com/ - */ - -function CompressedTexture( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) { - - Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ); - - this.image = { width: width, height: height }; - this.mipmaps = mipmaps; - - // no flipping for cube textures - // (also flipping doesn't work for compressed textures ) - - this.flipY = false; - - // can't generate mipmaps for compressed textures - // mips must be embedded in DDS files - - this.generateMipmaps = false; - -} - -CompressedTexture.prototype = Object.create( Texture.prototype ); -CompressedTexture.prototype.constructor = CompressedTexture; - -CompressedTexture.prototype.isCompressedTexture = true; - -/** - * @author Matt DesLauriers / @mattdesl - * @author atix / arthursilber.de - */ - -function DepthTexture( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) { - - format = format !== undefined ? format : DepthFormat; - - if ( format !== DepthFormat && format !== DepthStencilFormat ) { - - throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); - - } - - if ( type === undefined && format === DepthFormat ) type = UnsignedShortType; - if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type; - - Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); - - this.image = { width: width, height: height }; - - this.magFilter = magFilter !== undefined ? magFilter : NearestFilter; - this.minFilter = minFilter !== undefined ? minFilter : NearestFilter; - - this.flipY = false; - this.generateMipmaps = false; - -} - -DepthTexture.prototype = Object.create( Texture.prototype ); -DepthTexture.prototype.constructor = DepthTexture; -DepthTexture.prototype.isDepthTexture = true; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author Mugen87 / https://github.com/Mugen87 - */ - -function WireframeGeometry( geometry ) { - - BufferGeometry.call( this ); - - this.type = 'WireframeGeometry'; - - // buffer - - var vertices = []; - - // helper variables - - var i, j, l, o, ol; - var edge = [ 0, 0 ], edges = {}, e, edge1, edge2; - var key, keys = [ 'a', 'b', 'c' ]; - var vertex; - - // different logic for Geometry and BufferGeometry - - if ( geometry && geometry.isGeometry ) { - - // create a data structure that contains all edges without duplicates - - var faces = geometry.faces; - - for ( i = 0, l = faces.length; i < l; i ++ ) { - - var face = faces[ i ]; - - for ( j = 0; j < 3; j ++ ) { - - edge1 = face[ keys[ j ] ]; - edge2 = face[ keys[ ( j + 1 ) % 3 ] ]; - edge[ 0 ] = Math.min( edge1, edge2 ); // sorting prevents duplicates - edge[ 1 ] = Math.max( edge1, edge2 ); - - key = edge[ 0 ] + ',' + edge[ 1 ]; - - if ( edges[ key ] === undefined ) { - - edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ] }; - - } - - } - - } - - // generate vertices - - for ( key in edges ) { - - e = edges[ key ]; - - vertex = geometry.vertices[ e.index1 ]; - vertices.push( vertex.x, vertex.y, vertex.z ); - - vertex = geometry.vertices[ e.index2 ]; - vertices.push( vertex.x, vertex.y, vertex.z ); - - } - - } else if ( geometry && geometry.isBufferGeometry ) { - - var position, indices, groups; - var group, start, count; - var index1, index2; - - vertex = new Vector3(); - - if ( geometry.index !== null ) { - - // indexed BufferGeometry - - position = geometry.attributes.position; - indices = geometry.index; - groups = geometry.groups; - - if ( groups.length === 0 ) { - - groups = [ { start: 0, count: indices.count, materialIndex: 0 } ]; - - } - - // create a data structure that contains all eges without duplicates - - for ( o = 0, ol = groups.length; o < ol; ++ o ) { - - group = groups[ o ]; - - start = group.start; - count = group.count; - - for ( i = start, l = ( start + count ); i < l; i += 3 ) { - - for ( j = 0; j < 3; j ++ ) { - - edge1 = indices.getX( i + j ); - edge2 = indices.getX( i + ( j + 1 ) % 3 ); - edge[ 0 ] = Math.min( edge1, edge2 ); // sorting prevents duplicates - edge[ 1 ] = Math.max( edge1, edge2 ); - - key = edge[ 0 ] + ',' + edge[ 1 ]; - - if ( edges[ key ] === undefined ) { - - edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ] }; - - } - - } - - } - - } - - // generate vertices - - for ( key in edges ) { - - e = edges[ key ]; - - vertex.fromBufferAttribute( position, e.index1 ); - vertices.push( vertex.x, vertex.y, vertex.z ); - - vertex.fromBufferAttribute( position, e.index2 ); - vertices.push( vertex.x, vertex.y, vertex.z ); - - } - - } else { - - // non-indexed BufferGeometry - - position = geometry.attributes.position; - - for ( i = 0, l = ( position.count / 3 ); i < l; i ++ ) { - - for ( j = 0; j < 3; j ++ ) { - - // three edges per triangle, an edge is represented as (index1, index2) - // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0) - - index1 = 3 * i + j; - vertex.fromBufferAttribute( position, index1 ); - vertices.push( vertex.x, vertex.y, vertex.z ); - - index2 = 3 * i + ( ( j + 1 ) % 3 ); - vertex.fromBufferAttribute( position, index2 ); - vertices.push( vertex.x, vertex.y, vertex.z ); - - } - - } - - } - - } - - // build geometry - - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - -} - -WireframeGeometry.prototype = Object.create( BufferGeometry.prototype ); -WireframeGeometry.prototype.constructor = WireframeGeometry; - -/** - * @author zz85 / https://github.com/zz85 - * @author Mugen87 / https://github.com/Mugen87 - * - * Parametric Surfaces Geometry - * based on the brilliant article by @prideout http://prideout.net/blog/?p=44 - */ - -// ParametricGeometry - -function ParametricGeometry( func, slices, stacks ) { - - Geometry.call( this ); - - this.type = 'ParametricGeometry'; - - this.parameters = { - func: func, - slices: slices, - stacks: stacks - }; - - this.fromBufferGeometry( new ParametricBufferGeometry( func, slices, stacks ) ); - this.mergeVertices(); - -} - -ParametricGeometry.prototype = Object.create( Geometry.prototype ); -ParametricGeometry.prototype.constructor = ParametricGeometry; - -// ParametricBufferGeometry - -function ParametricBufferGeometry( func, slices, stacks ) { - - BufferGeometry.call( this ); - - this.type = 'ParametricBufferGeometry'; - - this.parameters = { - func: func, - slices: slices, - stacks: stacks - }; - - // buffers - - var indices = []; - var vertices = []; - var normals = []; - var uvs = []; - - var EPS = 0.00001; - - var normal = new Vector3(); - - var p0 = new Vector3(), p1 = new Vector3(); - var pu = new Vector3(), pv = new Vector3(); - - var i, j; - - // generate vertices, normals and uvs - - var sliceCount = slices + 1; - - for ( i = 0; i <= stacks; i ++ ) { - - var v = i / stacks; - - for ( j = 0; j <= slices; j ++ ) { - - var u = j / slices; - - // vertex - - p0 = func( u, v, p0 ); - vertices.push( p0.x, p0.y, p0.z ); - - // normal - - // approximate tangent vectors via finite differences - - if ( u - EPS >= 0 ) { - - p1 = func( u - EPS, v, p1 ); - pu.subVectors( p0, p1 ); - - } else { - - p1 = func( u + EPS, v, p1 ); - pu.subVectors( p1, p0 ); - - } - - if ( v - EPS >= 0 ) { - - p1 = func( u, v - EPS, p1 ); - pv.subVectors( p0, p1 ); - - } else { - - p1 = func( u, v + EPS, p1 ); - pv.subVectors( p1, p0 ); - - } - - // cross product of tangent vectors returns surface normal - - normal.crossVectors( pu, pv ).normalize(); - normals.push( normal.x, normal.y, normal.z ); - - // uv - - uvs.push( u, v ); - - } - - } - - // generate indices - - for ( i = 0; i < stacks; i ++ ) { - - for ( j = 0; j < slices; j ++ ) { - - var a = i * sliceCount + j; - var b = i * sliceCount + j + 1; - var c = ( i + 1 ) * sliceCount + j + 1; - var d = ( i + 1 ) * sliceCount + j; - - // faces one and two - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - -} - -ParametricBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -ParametricBufferGeometry.prototype.constructor = ParametricBufferGeometry; - -/** - * @author clockworkgeek / https://github.com/clockworkgeek - * @author timothypratley / https://github.com/timothypratley - * @author WestLangley / http://github.com/WestLangley - * @author Mugen87 / https://github.com/Mugen87 - */ - -// PolyhedronGeometry - -function PolyhedronGeometry( vertices, indices, radius, detail ) { - - Geometry.call( this ); - - this.type = 'PolyhedronGeometry'; - - this.parameters = { - vertices: vertices, - indices: indices, - radius: radius, - detail: detail - }; - - this.fromBufferGeometry( new PolyhedronBufferGeometry( vertices, indices, radius, detail ) ); - this.mergeVertices(); - -} - -PolyhedronGeometry.prototype = Object.create( Geometry.prototype ); -PolyhedronGeometry.prototype.constructor = PolyhedronGeometry; - -// PolyhedronBufferGeometry - -function PolyhedronBufferGeometry( vertices, indices, radius, detail ) { - - BufferGeometry.call( this ); - - this.type = 'PolyhedronBufferGeometry'; - - this.parameters = { - vertices: vertices, - indices: indices, - radius: radius, - detail: detail - }; - - radius = radius || 1; - detail = detail || 0; - - // default buffer data - - var vertexBuffer = []; - var uvBuffer = []; - - // the subdivision creates the vertex buffer data - - subdivide( detail ); - - // all vertices should lie on a conceptual sphere with a given radius - - appplyRadius( radius ); - - // finally, create the uv data - - generateUVs(); - - // build non-indexed geometry - - this.addAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); - - if ( detail === 0 ) { - - this.computeVertexNormals(); // flat normals - - } else { - - this.normalizeNormals(); // smooth normals - - } - - // helper functions - - function subdivide( detail ) { - - var a = new Vector3(); - var b = new Vector3(); - var c = new Vector3(); - - // iterate over all faces and apply a subdivison with the given detail value - - for ( var i = 0; i < indices.length; i += 3 ) { - - // get the vertices of the face - - getVertexByIndex( indices[ i + 0 ], a ); - getVertexByIndex( indices[ i + 1 ], b ); - getVertexByIndex( indices[ i + 2 ], c ); - - // perform subdivision - - subdivideFace( a, b, c, detail ); - - } - - } - - function subdivideFace( a, b, c, detail ) { - - var cols = Math.pow( 2, detail ); - - // we use this multidimensional array as a data structure for creating the subdivision - - var v = []; - - var i, j; - - // construct all of the vertices for this subdivision - - for ( i = 0; i <= cols; i ++ ) { - - v[ i ] = []; - - var aj = a.clone().lerp( c, i / cols ); - var bj = b.clone().lerp( c, i / cols ); - - var rows = cols - i; - - for ( j = 0; j <= rows; j ++ ) { - - if ( j === 0 && i === cols ) { - - v[ i ][ j ] = aj; - - } else { - - v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); - - } - - } - - } - - // construct all of the faces - - for ( i = 0; i < cols; i ++ ) { - - for ( j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { - - var k = Math.floor( j / 2 ); - - if ( j % 2 === 0 ) { - - pushVertex( v[ i ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k ] ); - pushVertex( v[ i ][ k ] ); - - } else { - - pushVertex( v[ i ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k + 1 ] ); - pushVertex( v[ i + 1 ][ k ] ); - - } - - } - - } - - } - - function appplyRadius( radius ) { - - var vertex = new Vector3(); - - // iterate over the entire buffer and apply the radius to each vertex - - for ( var i = 0; i < vertexBuffer.length; i += 3 ) { - - vertex.x = vertexBuffer[ i + 0 ]; - vertex.y = vertexBuffer[ i + 1 ]; - vertex.z = vertexBuffer[ i + 2 ]; - - vertex.normalize().multiplyScalar( radius ); - - vertexBuffer[ i + 0 ] = vertex.x; - vertexBuffer[ i + 1 ] = vertex.y; - vertexBuffer[ i + 2 ] = vertex.z; - - } - - } - - function generateUVs() { - - var vertex = new Vector3(); - - for ( var i = 0; i < vertexBuffer.length; i += 3 ) { - - vertex.x = vertexBuffer[ i + 0 ]; - vertex.y = vertexBuffer[ i + 1 ]; - vertex.z = vertexBuffer[ i + 2 ]; - - var u = azimuth( vertex ) / 2 / Math.PI + 0.5; - var v = inclination( vertex ) / Math.PI + 0.5; - uvBuffer.push( u, 1 - v ); - - } - - correctUVs(); - - correctSeam(); - - } - - function correctSeam() { - - // handle case when face straddles the seam, see #3269 - - for ( var i = 0; i < uvBuffer.length; i += 6 ) { - - // uv data of a single face - - var x0 = uvBuffer[ i + 0 ]; - var x1 = uvBuffer[ i + 2 ]; - var x2 = uvBuffer[ i + 4 ]; - - var max = Math.max( x0, x1, x2 ); - var min = Math.min( x0, x1, x2 ); - - // 0.9 is somewhat arbitrary - - if ( max > 0.9 && min < 0.1 ) { - - if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; - if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; - if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; - - } - - } - - } - - function pushVertex( vertex ) { - - vertexBuffer.push( vertex.x, vertex.y, vertex.z ); - - } - - function getVertexByIndex( index, vertex ) { - - var stride = index * 3; - - vertex.x = vertices[ stride + 0 ]; - vertex.y = vertices[ stride + 1 ]; - vertex.z = vertices[ stride + 2 ]; - - } - - function correctUVs() { - - var a = new Vector3(); - var b = new Vector3(); - var c = new Vector3(); - - var centroid = new Vector3(); - - var uvA = new Vector2(); - var uvB = new Vector2(); - var uvC = new Vector2(); - - for ( var i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { - - a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); - b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); - c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); - - uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); - uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); - uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); - - centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); - - var azi = azimuth( centroid ); - - correctUV( uvA, j + 0, a, azi ); - correctUV( uvB, j + 2, b, azi ); - correctUV( uvC, j + 4, c, azi ); - - } - - } - - function correctUV( uv, stride, vector, azimuth ) { - - if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { - - uvBuffer[ stride ] = uv.x - 1; - - } - - if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { - - uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; - - } - - } - - // Angle around the Y axis, counter-clockwise when looking from above. - - function azimuth( vector ) { - - return Math.atan2( vector.z, - vector.x ); - - } - - - // Angle above the XZ plane. - - function inclination( vector ) { - - return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); - - } - -} - -PolyhedronBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -PolyhedronBufferGeometry.prototype.constructor = PolyhedronBufferGeometry; - -/** - * @author timothypratley / https://github.com/timothypratley - * @author Mugen87 / https://github.com/Mugen87 - */ - -// TetrahedronGeometry - -function TetrahedronGeometry( radius, detail ) { - - Geometry.call( this ); - - this.type = 'TetrahedronGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - - this.fromBufferGeometry( new TetrahedronBufferGeometry( radius, detail ) ); - this.mergeVertices(); - -} - -TetrahedronGeometry.prototype = Object.create( Geometry.prototype ); -TetrahedronGeometry.prototype.constructor = TetrahedronGeometry; - -// TetrahedronBufferGeometry - -function TetrahedronBufferGeometry( radius, detail ) { - - var vertices = [ - 1, 1, 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, - 1 - ]; - - var indices = [ - 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 - ]; - - PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail ); - - this.type = 'TetrahedronBufferGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - -} - -TetrahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype ); -TetrahedronBufferGeometry.prototype.constructor = TetrahedronBufferGeometry; - -/** - * @author timothypratley / https://github.com/timothypratley - * @author Mugen87 / https://github.com/Mugen87 - */ - -// OctahedronGeometry - -function OctahedronGeometry( radius, detail ) { - - Geometry.call( this ); - - this.type = 'OctahedronGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - - this.fromBufferGeometry( new OctahedronBufferGeometry( radius, detail ) ); - this.mergeVertices(); - -} - -OctahedronGeometry.prototype = Object.create( Geometry.prototype ); -OctahedronGeometry.prototype.constructor = OctahedronGeometry; - -// OctahedronBufferGeometry - -function OctahedronBufferGeometry( radius, detail ) { - - var vertices = [ - 1, 0, 0, - 1, 0, 0, 0, 1, 0, - 0, - 1, 0, 0, 0, 1, 0, 0, - 1 - ]; - - var indices = [ - 0, 2, 4, 0, 4, 3, 0, 3, 5, - 0, 5, 2, 1, 2, 5, 1, 5, 3, - 1, 3, 4, 1, 4, 2 - ]; - - PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail ); - - this.type = 'OctahedronBufferGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - -} - -OctahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype ); -OctahedronBufferGeometry.prototype.constructor = OctahedronBufferGeometry; - -/** - * @author timothypratley / https://github.com/timothypratley - * @author Mugen87 / https://github.com/Mugen87 - */ - -// IcosahedronGeometry - -function IcosahedronGeometry( radius, detail ) { - - Geometry.call( this ); - - this.type = 'IcosahedronGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - - this.fromBufferGeometry( new IcosahedronBufferGeometry( radius, detail ) ); - this.mergeVertices(); - -} - -IcosahedronGeometry.prototype = Object.create( Geometry.prototype ); -IcosahedronGeometry.prototype.constructor = IcosahedronGeometry; - -// IcosahedronBufferGeometry - -function IcosahedronBufferGeometry( radius, detail ) { - - var t = ( 1 + Math.sqrt( 5 ) ) / 2; - - var vertices = [ - - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, 0, - 0, - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, - t, 0, - 1, t, 0, 1, - t, 0, - 1, - t, 0, 1 - ]; - - var indices = [ - 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, - 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, - 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, - 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 - ]; - - PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail ); - - this.type = 'IcosahedronBufferGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - -} - -IcosahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype ); -IcosahedronBufferGeometry.prototype.constructor = IcosahedronBufferGeometry; - -/** - * @author Abe Pazos / https://hamoid.com - * @author Mugen87 / https://github.com/Mugen87 - */ - -// DodecahedronGeometry - -function DodecahedronGeometry( radius, detail ) { - - Geometry.call( this ); - - this.type = 'DodecahedronGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - - this.fromBufferGeometry( new DodecahedronBufferGeometry( radius, detail ) ); - this.mergeVertices(); - -} - -DodecahedronGeometry.prototype = Object.create( Geometry.prototype ); -DodecahedronGeometry.prototype.constructor = DodecahedronGeometry; - -// DodecahedronBufferGeometry - -function DodecahedronBufferGeometry( radius, detail ) { - - var t = ( 1 + Math.sqrt( 5 ) ) / 2; - var r = 1 / t; - - var vertices = [ - - // (±1, ±1, ±1) - - 1, - 1, - 1, - 1, - 1, 1, - - 1, 1, - 1, - 1, 1, 1, - 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, 1, 1, 1, - - // (0, ±1/φ, ±φ) - 0, - r, - t, 0, - r, t, - 0, r, - t, 0, r, t, - - // (±1/φ, ±φ, 0) - - r, - t, 0, - r, t, 0, - r, - t, 0, r, t, 0, - - // (±φ, 0, ±1/φ) - - t, 0, - r, t, 0, - r, - - t, 0, r, t, 0, r - ]; - - var indices = [ - 3, 11, 7, 3, 7, 15, 3, 15, 13, - 7, 19, 17, 7, 17, 6, 7, 6, 15, - 17, 4, 8, 17, 8, 10, 17, 10, 6, - 8, 0, 16, 8, 16, 2, 8, 2, 10, - 0, 12, 1, 0, 1, 18, 0, 18, 16, - 6, 10, 2, 6, 2, 13, 6, 13, 15, - 2, 16, 18, 2, 18, 3, 2, 3, 13, - 18, 1, 9, 18, 9, 11, 18, 11, 3, - 4, 14, 12, 4, 12, 0, 4, 0, 8, - 11, 9, 5, 11, 5, 19, 11, 19, 7, - 19, 5, 14, 19, 14, 4, 19, 4, 17, - 1, 12, 14, 1, 14, 5, 1, 5, 9 - ]; - - PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail ); - - this.type = 'DodecahedronBufferGeometry'; - - this.parameters = { - radius: radius, - detail: detail - }; - -} - -DodecahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype ); -DodecahedronBufferGeometry.prototype.constructor = DodecahedronBufferGeometry; - -/** - * @author oosmoxiecode / https://github.com/oosmoxiecode - * @author WestLangley / https://github.com/WestLangley - * @author zz85 / https://github.com/zz85 - * @author miningold / https://github.com/miningold - * @author jonobr1 / https://github.com/jonobr1 - * @author Mugen87 / https://github.com/Mugen87 - * - */ - -// TubeGeometry - -function TubeGeometry( path, tubularSegments, radius, radialSegments, closed, taper ) { - - Geometry.call( this ); - - this.type = 'TubeGeometry'; - - this.parameters = { - path: path, - tubularSegments: tubularSegments, - radius: radius, - radialSegments: radialSegments, - closed: closed - }; - - if ( taper !== undefined ) console.warn( 'THREE.TubeGeometry: taper has been removed.' ); - - var bufferGeometry = new TubeBufferGeometry( path, tubularSegments, radius, radialSegments, closed ); - - // expose internals - - this.tangents = bufferGeometry.tangents; - this.normals = bufferGeometry.normals; - this.binormals = bufferGeometry.binormals; - - // create geometry - - this.fromBufferGeometry( bufferGeometry ); - this.mergeVertices(); - -} - -TubeGeometry.prototype = Object.create( Geometry.prototype ); -TubeGeometry.prototype.constructor = TubeGeometry; - -// TubeBufferGeometry - -function TubeBufferGeometry( path, tubularSegments, radius, radialSegments, closed ) { - - BufferGeometry.call( this ); - - this.type = 'TubeBufferGeometry'; - - this.parameters = { - path: path, - tubularSegments: tubularSegments, - radius: radius, - radialSegments: radialSegments, - closed: closed - }; - - tubularSegments = tubularSegments || 64; - radius = radius || 1; - radialSegments = radialSegments || 8; - closed = closed || false; - - var frames = path.computeFrenetFrames( tubularSegments, closed ); - - // expose internals - - this.tangents = frames.tangents; - this.normals = frames.normals; - this.binormals = frames.binormals; - - // helper variables - - var vertex = new Vector3(); - var normal = new Vector3(); - var uv = new Vector2(); - var P = new Vector3(); - - var i, j; - - // buffer - - var vertices = []; - var normals = []; - var uvs = []; - var indices = []; - - // create buffer data - - generateBufferData(); - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - // functions - - function generateBufferData() { - - for ( i = 0; i < tubularSegments; i ++ ) { - - generateSegment( i ); - - } - - // if the geometry is not closed, generate the last row of vertices and normals - // at the regular position on the given path - // - // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ) - - generateSegment( ( closed === false ) ? tubularSegments : 0 ); - - // uvs are generated in a separate function. - // this makes it easy compute correct values for closed geometries - - generateUVs(); - - // finally create faces - - generateIndices(); - - } - - function generateSegment( i ) { - - // we use getPointAt to sample evenly distributed points from the given path - - P = path.getPointAt( i / tubularSegments, P ); - - // retrieve corresponding normal and binormal - - var N = frames.normals[ i ]; - var B = frames.binormals[ i ]; - - // generate normals and vertices for the current segment - - for ( j = 0; j <= radialSegments; j ++ ) { - - var v = j / radialSegments * Math.PI * 2; - - var sin = Math.sin( v ); - var cos = - Math.cos( v ); - - // normal - - normal.x = ( cos * N.x + sin * B.x ); - normal.y = ( cos * N.y + sin * B.y ); - normal.z = ( cos * N.z + sin * B.z ); - normal.normalize(); - - normals.push( normal.x, normal.y, normal.z ); - - // vertex - - vertex.x = P.x + radius * normal.x; - vertex.y = P.y + radius * normal.y; - vertex.z = P.z + radius * normal.z; - - vertices.push( vertex.x, vertex.y, vertex.z ); - - } - - } - - function generateIndices() { - - for ( j = 1; j <= tubularSegments; j ++ ) { - - for ( i = 1; i <= radialSegments; i ++ ) { - - var a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); - var b = ( radialSegments + 1 ) * j + ( i - 1 ); - var c = ( radialSegments + 1 ) * j + i; - var d = ( radialSegments + 1 ) * ( j - 1 ) + i; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - } - - function generateUVs() { - - for ( i = 0; i <= tubularSegments; i ++ ) { - - for ( j = 0; j <= radialSegments; j ++ ) { - - uv.x = i / tubularSegments; - uv.y = j / radialSegments; - - uvs.push( uv.x, uv.y ); - - } - - } - - } - -} - -TubeBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -TubeBufferGeometry.prototype.constructor = TubeBufferGeometry; - -/** - * @author oosmoxiecode - * @author Mugen87 / https://github.com/Mugen87 - * - * based on http://www.blackpawn.com/texts/pqtorus/ - */ - -// TorusKnotGeometry - -function TorusKnotGeometry( radius, tube, tubularSegments, radialSegments, p, q, heightScale ) { - - Geometry.call( this ); - - this.type = 'TorusKnotGeometry'; - - this.parameters = { - radius: radius, - tube: tube, - tubularSegments: tubularSegments, - radialSegments: radialSegments, - p: p, - q: q - }; - - if ( heightScale !== undefined ) console.warn( 'THREE.TorusKnotGeometry: heightScale has been deprecated. Use .scale( x, y, z ) instead.' ); - - this.fromBufferGeometry( new TorusKnotBufferGeometry( radius, tube, tubularSegments, radialSegments, p, q ) ); - this.mergeVertices(); - -} - -TorusKnotGeometry.prototype = Object.create( Geometry.prototype ); -TorusKnotGeometry.prototype.constructor = TorusKnotGeometry; - -// TorusKnotBufferGeometry - -function TorusKnotBufferGeometry( radius, tube, tubularSegments, radialSegments, p, q ) { - - BufferGeometry.call( this ); - - this.type = 'TorusKnotBufferGeometry'; - - this.parameters = { - radius: radius, - tube: tube, - tubularSegments: tubularSegments, - radialSegments: radialSegments, - p: p, - q: q - }; - - radius = radius || 1; - tube = tube || 0.4; - tubularSegments = Math.floor( tubularSegments ) || 64; - radialSegments = Math.floor( radialSegments ) || 8; - p = p || 2; - q = q || 3; - - // buffers - - var indices = []; - var vertices = []; - var normals = []; - var uvs = []; - - // helper variables - - var i, j; - - var vertex = new Vector3(); - var normal = new Vector3(); - - var P1 = new Vector3(); - var P2 = new Vector3(); - - var B = new Vector3(); - var T = new Vector3(); - var N = new Vector3(); - - // generate vertices, normals and uvs - - for ( i = 0; i <= tubularSegments; ++ i ) { - - // the radian "u" is used to calculate the position on the torus curve of the current tubular segement - - var u = i / tubularSegments * p * Math.PI * 2; - - // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead. - // these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions - - calculatePositionOnCurve( u, p, q, radius, P1 ); - calculatePositionOnCurve( u + 0.01, p, q, radius, P2 ); - - // calculate orthonormal basis - - T.subVectors( P2, P1 ); - N.addVectors( P2, P1 ); - B.crossVectors( T, N ); - N.crossVectors( B, T ); - - // normalize B, N. T can be ignored, we don't use it - - B.normalize(); - N.normalize(); - - for ( j = 0; j <= radialSegments; ++ j ) { - - // now calculate the vertices. they are nothing more than an extrusion of the torus curve. - // because we extrude a shape in the xy-plane, there is no need to calculate a z-value. - - var v = j / radialSegments * Math.PI * 2; - var cx = - tube * Math.cos( v ); - var cy = tube * Math.sin( v ); - - // now calculate the final vertex position. - // first we orient the extrusion with our basis vectos, then we add it to the current position on the curve - - vertex.x = P1.x + ( cx * N.x + cy * B.x ); - vertex.y = P1.y + ( cx * N.y + cy * B.y ); - vertex.z = P1.z + ( cx * N.z + cy * B.z ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal) - - normal.subVectors( vertex, P1 ).normalize(); - - normals.push( normal.x, normal.y, normal.z ); - - // uv - - uvs.push( i / tubularSegments ); - uvs.push( j / radialSegments ); - - } - - } - - // generate indices - - for ( j = 1; j <= tubularSegments; j ++ ) { - - for ( i = 1; i <= radialSegments; i ++ ) { - - // indices - - var a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); - var b = ( radialSegments + 1 ) * j + ( i - 1 ); - var c = ( radialSegments + 1 ) * j + i; - var d = ( radialSegments + 1 ) * ( j - 1 ) + i; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - // this function calculates the current position on the torus curve - - function calculatePositionOnCurve( u, p, q, radius, position ) { - - var cu = Math.cos( u ); - var su = Math.sin( u ); - var quOverP = q / p * u; - var cs = Math.cos( quOverP ); - - position.x = radius * ( 2 + cs ) * 0.5 * cu; - position.y = radius * ( 2 + cs ) * su * 0.5; - position.z = radius * Math.sin( quOverP ) * 0.5; - - } - -} - -TorusKnotBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -TorusKnotBufferGeometry.prototype.constructor = TorusKnotBufferGeometry; - -/** - * @author oosmoxiecode - * @author mrdoob / http://mrdoob.com/ - * @author Mugen87 / https://github.com/Mugen87 - */ - -// TorusGeometry - -function TorusGeometry( radius, tube, radialSegments, tubularSegments, arc ) { - - Geometry.call( this ); - - this.type = 'TorusGeometry'; - - this.parameters = { - radius: radius, - tube: tube, - radialSegments: radialSegments, - tubularSegments: tubularSegments, - arc: arc - }; - - this.fromBufferGeometry( new TorusBufferGeometry( radius, tube, radialSegments, tubularSegments, arc ) ); - this.mergeVertices(); - -} - -TorusGeometry.prototype = Object.create( Geometry.prototype ); -TorusGeometry.prototype.constructor = TorusGeometry; - -// TorusBufferGeometry - -function TorusBufferGeometry( radius, tube, radialSegments, tubularSegments, arc ) { - - BufferGeometry.call( this ); - - this.type = 'TorusBufferGeometry'; - - this.parameters = { - radius: radius, - tube: tube, - radialSegments: radialSegments, - tubularSegments: tubularSegments, - arc: arc - }; - - radius = radius || 1; - tube = tube || 0.4; - radialSegments = Math.floor( radialSegments ) || 8; - tubularSegments = Math.floor( tubularSegments ) || 6; - arc = arc || Math.PI * 2; - - // buffers - - var indices = []; - var vertices = []; - var normals = []; - var uvs = []; - - // helper variables - - var center = new Vector3(); - var vertex = new Vector3(); - var normal = new Vector3(); - - var j, i; - - // generate vertices, normals and uvs - - for ( j = 0; j <= radialSegments; j ++ ) { - - for ( i = 0; i <= tubularSegments; i ++ ) { - - var u = i / tubularSegments * arc; - var v = j / radialSegments * Math.PI * 2; - - // vertex - - vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); - vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); - vertex.z = tube * Math.sin( v ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - center.x = radius * Math.cos( u ); - center.y = radius * Math.sin( u ); - normal.subVectors( vertex, center ).normalize(); - - normals.push( normal.x, normal.y, normal.z ); - - // uv - - uvs.push( i / tubularSegments ); - uvs.push( j / radialSegments ); - - } - - } - - // generate indices - - for ( j = 1; j <= radialSegments; j ++ ) { - - for ( i = 1; i <= tubularSegments; i ++ ) { - - // indices - - var a = ( tubularSegments + 1 ) * j + i - 1; - var b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; - var c = ( tubularSegments + 1 ) * ( j - 1 ) + i; - var d = ( tubularSegments + 1 ) * j + i; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - -} - -TorusBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -TorusBufferGeometry.prototype.constructor = TorusBufferGeometry; - -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - */ - -var ShapeUtils = { - - // calculate area of the contour polygon - - area: function ( contour ) { - - var n = contour.length; - var a = 0.0; - - for ( var p = n - 1, q = 0; q < n; p = q ++ ) { - - a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; - - } - - return a * 0.5; - - }, - - triangulate: ( function () { - - /** - * This code is a quick port of code written in C++ which was submitted to - * flipcode.com by John W. Ratcliff // July 22, 2000 - * See original code and more information here: - * http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml - * - * ported to actionscript by Zevan Rosser - * www.actionsnippet.com - * - * ported to javascript by Joshua Koo - * http://www.lab4games.net/zz85/blog - * - */ - - function snip( contour, u, v, w, n, verts ) { - - var p; - var ax, ay, bx, by; - var cx, cy, px, py; - - ax = contour[ verts[ u ] ].x; - ay = contour[ verts[ u ] ].y; - - bx = contour[ verts[ v ] ].x; - by = contour[ verts[ v ] ].y; - - cx = contour[ verts[ w ] ].x; - cy = contour[ verts[ w ] ].y; - - if ( ( bx - ax ) * ( cy - ay ) - ( by - ay ) * ( cx - ax ) <= 0 ) return false; - - var aX, aY, bX, bY, cX, cY; - var apx, apy, bpx, bpy, cpx, cpy; - var cCROSSap, bCROSScp, aCROSSbp; - - aX = cx - bx; aY = cy - by; - bX = ax - cx; bY = ay - cy; - cX = bx - ax; cY = by - ay; - - for ( p = 0; p < n; p ++ ) { - - px = contour[ verts[ p ] ].x; - py = contour[ verts[ p ] ].y; - - if ( ( ( px === ax ) && ( py === ay ) ) || - ( ( px === bx ) && ( py === by ) ) || - ( ( px === cx ) && ( py === cy ) ) ) continue; - - apx = px - ax; apy = py - ay; - bpx = px - bx; bpy = py - by; - cpx = px - cx; cpy = py - cy; - - // see if p is inside triangle abc - - aCROSSbp = aX * bpy - aY * bpx; - cCROSSap = cX * apy - cY * apx; - bCROSScp = bX * cpy - bY * cpx; - - if ( ( aCROSSbp >= - Number.EPSILON ) && ( bCROSScp >= - Number.EPSILON ) && ( cCROSSap >= - Number.EPSILON ) ) return false; - - } - - return true; - - } - - // takes in an contour array and returns - - return function triangulate( contour, indices ) { - - var n = contour.length; - - if ( n < 3 ) return null; - - var result = [], - verts = [], - vertIndices = []; - - /* we want a counter-clockwise polygon in verts */ - - var u, v, w; - - if ( ShapeUtils.area( contour ) > 0.0 ) { - - for ( v = 0; v < n; v ++ ) verts[ v ] = v; - - } else { - - for ( v = 0; v < n; v ++ ) verts[ v ] = ( n - 1 ) - v; - - } - - var nv = n; - - /* remove nv - 2 vertices, creating 1 triangle every time */ - - var count = 2 * nv; /* error detection */ - - for ( v = nv - 1; nv > 2; ) { - - /* if we loop, it is probably a non-simple polygon */ - - if ( ( count -- ) <= 0 ) { - - //** Triangulate: ERROR - probable bad polygon! - - //throw ( "Warning, unable to triangulate polygon!" ); - //return null; - // Sometimes warning is fine, especially polygons are triangulated in reverse. - console.warn( 'THREE.ShapeUtils: Unable to triangulate polygon! in triangulate()' ); - - if ( indices ) return vertIndices; - return result; - - } - - /* three consecutive vertices in current polygon, */ - - u = v; if ( nv <= u ) u = 0; /* previous */ - v = u + 1; if ( nv <= v ) v = 0; /* new v */ - w = v + 1; if ( nv <= w ) w = 0; /* next */ - - if ( snip( contour, u, v, w, nv, verts ) ) { - - var a, b, c, s, t; - - /* true names of the vertices */ - - a = verts[ u ]; - b = verts[ v ]; - c = verts[ w ]; - - /* output Triangle */ - - result.push( [ contour[ a ], - contour[ b ], - contour[ c ] ] ); - - - vertIndices.push( [ verts[ u ], verts[ v ], verts[ w ] ] ); - - /* remove v from the remaining polygon */ - - for ( s = v, t = v + 1; t < nv; s ++, t ++ ) { - - verts[ s ] = verts[ t ]; - - } - - nv --; - - /* reset error detection counter */ - - count = 2 * nv; - - } - - } - - if ( indices ) return vertIndices; - return result; - - }; - - } )(), - - triangulateShape: function ( contour, holes ) { - - function removeDupEndPts( points ) { - - var l = points.length; - - if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { - - points.pop(); - - } - - } - - removeDupEndPts( contour ); - holes.forEach( removeDupEndPts ); - - function point_in_segment_2D_colin( inSegPt1, inSegPt2, inOtherPt ) { - - // inOtherPt needs to be collinear to the inSegment - if ( inSegPt1.x !== inSegPt2.x ) { - - if ( inSegPt1.x < inSegPt2.x ) { - - return ( ( inSegPt1.x <= inOtherPt.x ) && ( inOtherPt.x <= inSegPt2.x ) ); - - } else { - - return ( ( inSegPt2.x <= inOtherPt.x ) && ( inOtherPt.x <= inSegPt1.x ) ); - - } - - } else { - - if ( inSegPt1.y < inSegPt2.y ) { - - return ( ( inSegPt1.y <= inOtherPt.y ) && ( inOtherPt.y <= inSegPt2.y ) ); - - } else { - - return ( ( inSegPt2.y <= inOtherPt.y ) && ( inOtherPt.y <= inSegPt1.y ) ); - - } - - } - - } - - function intersect_segments_2D( inSeg1Pt1, inSeg1Pt2, inSeg2Pt1, inSeg2Pt2, inExcludeAdjacentSegs ) { - - var seg1dx = inSeg1Pt2.x - inSeg1Pt1.x, seg1dy = inSeg1Pt2.y - inSeg1Pt1.y; - var seg2dx = inSeg2Pt2.x - inSeg2Pt1.x, seg2dy = inSeg2Pt2.y - inSeg2Pt1.y; - - var seg1seg2dx = inSeg1Pt1.x - inSeg2Pt1.x; - var seg1seg2dy = inSeg1Pt1.y - inSeg2Pt1.y; - - var limit = seg1dy * seg2dx - seg1dx * seg2dy; - var perpSeg1 = seg1dy * seg1seg2dx - seg1dx * seg1seg2dy; - - if ( Math.abs( limit ) > Number.EPSILON ) { - - // not parallel - - var perpSeg2; - if ( limit > 0 ) { - - if ( ( perpSeg1 < 0 ) || ( perpSeg1 > limit ) ) return []; - perpSeg2 = seg2dy * seg1seg2dx - seg2dx * seg1seg2dy; - if ( ( perpSeg2 < 0 ) || ( perpSeg2 > limit ) ) return []; - - } else { - - if ( ( perpSeg1 > 0 ) || ( perpSeg1 < limit ) ) return []; - perpSeg2 = seg2dy * seg1seg2dx - seg2dx * seg1seg2dy; - if ( ( perpSeg2 > 0 ) || ( perpSeg2 < limit ) ) return []; - - } - - // i.e. to reduce rounding errors - // intersection at endpoint of segment#1? - if ( perpSeg2 === 0 ) { - - if ( ( inExcludeAdjacentSegs ) && - ( ( perpSeg1 === 0 ) || ( perpSeg1 === limit ) ) ) return []; - return [ inSeg1Pt1 ]; - - } - if ( perpSeg2 === limit ) { - - if ( ( inExcludeAdjacentSegs ) && - ( ( perpSeg1 === 0 ) || ( perpSeg1 === limit ) ) ) return []; - return [ inSeg1Pt2 ]; - - } - // intersection at endpoint of segment#2? - if ( perpSeg1 === 0 ) return [ inSeg2Pt1 ]; - if ( perpSeg1 === limit ) return [ inSeg2Pt2 ]; - - // return real intersection point - var factorSeg1 = perpSeg2 / limit; - return [ { x: inSeg1Pt1.x + factorSeg1 * seg1dx, y: inSeg1Pt1.y + factorSeg1 * seg1dy } ]; - - } else { - - // parallel or collinear - if ( ( perpSeg1 !== 0 ) || - ( seg2dy * seg1seg2dx !== seg2dx * seg1seg2dy ) ) return []; - - // they are collinear or degenerate - var seg1Pt = ( ( seg1dx === 0 ) && ( seg1dy === 0 ) ); // segment1 is just a point? - var seg2Pt = ( ( seg2dx === 0 ) && ( seg2dy === 0 ) ); // segment2 is just a point? - // both segments are points - if ( seg1Pt && seg2Pt ) { - - if ( ( inSeg1Pt1.x !== inSeg2Pt1.x ) || - ( inSeg1Pt1.y !== inSeg2Pt1.y ) ) return []; // they are distinct points - return [ inSeg1Pt1 ]; // they are the same point - - } - // segment#1 is a single point - if ( seg1Pt ) { - - if ( ! point_in_segment_2D_colin( inSeg2Pt1, inSeg2Pt2, inSeg1Pt1 ) ) return []; // but not in segment#2 - return [ inSeg1Pt1 ]; - - } - // segment#2 is a single point - if ( seg2Pt ) { - - if ( ! point_in_segment_2D_colin( inSeg1Pt1, inSeg1Pt2, inSeg2Pt1 ) ) return []; // but not in segment#1 - return [ inSeg2Pt1 ]; - - } - - // they are collinear segments, which might overlap - var seg1min, seg1max, seg1minVal, seg1maxVal; - var seg2min, seg2max, seg2minVal, seg2maxVal; - if ( seg1dx !== 0 ) { - - // the segments are NOT on a vertical line - if ( inSeg1Pt1.x < inSeg1Pt2.x ) { - - seg1min = inSeg1Pt1; seg1minVal = inSeg1Pt1.x; - seg1max = inSeg1Pt2; seg1maxVal = inSeg1Pt2.x; - - } else { - - seg1min = inSeg1Pt2; seg1minVal = inSeg1Pt2.x; - seg1max = inSeg1Pt1; seg1maxVal = inSeg1Pt1.x; - - } - if ( inSeg2Pt1.x < inSeg2Pt2.x ) { - - seg2min = inSeg2Pt1; seg2minVal = inSeg2Pt1.x; - seg2max = inSeg2Pt2; seg2maxVal = inSeg2Pt2.x; - - } else { - - seg2min = inSeg2Pt2; seg2minVal = inSeg2Pt2.x; - seg2max = inSeg2Pt1; seg2maxVal = inSeg2Pt1.x; - - } - - } else { - - // the segments are on a vertical line - if ( inSeg1Pt1.y < inSeg1Pt2.y ) { - - seg1min = inSeg1Pt1; seg1minVal = inSeg1Pt1.y; - seg1max = inSeg1Pt2; seg1maxVal = inSeg1Pt2.y; - - } else { - - seg1min = inSeg1Pt2; seg1minVal = inSeg1Pt2.y; - seg1max = inSeg1Pt1; seg1maxVal = inSeg1Pt1.y; - - } - if ( inSeg2Pt1.y < inSeg2Pt2.y ) { - - seg2min = inSeg2Pt1; seg2minVal = inSeg2Pt1.y; - seg2max = inSeg2Pt2; seg2maxVal = inSeg2Pt2.y; - - } else { - - seg2min = inSeg2Pt2; seg2minVal = inSeg2Pt2.y; - seg2max = inSeg2Pt1; seg2maxVal = inSeg2Pt1.y; - - } - - } - if ( seg1minVal <= seg2minVal ) { - - if ( seg1maxVal < seg2minVal ) return []; - if ( seg1maxVal === seg2minVal ) { - - if ( inExcludeAdjacentSegs ) return []; - return [ seg2min ]; - - } - if ( seg1maxVal <= seg2maxVal ) return [ seg2min, seg1max ]; - return [ seg2min, seg2max ]; - - } else { - - if ( seg1minVal > seg2maxVal ) return []; - if ( seg1minVal === seg2maxVal ) { - - if ( inExcludeAdjacentSegs ) return []; - return [ seg1min ]; - - } - if ( seg1maxVal <= seg2maxVal ) return [ seg1min, seg1max ]; - return [ seg1min, seg2max ]; - - } - - } - - } - - function isPointInsideAngle( inVertex, inLegFromPt, inLegToPt, inOtherPt ) { - - // The order of legs is important - - // translation of all points, so that Vertex is at (0,0) - var legFromPtX = inLegFromPt.x - inVertex.x, legFromPtY = inLegFromPt.y - inVertex.y; - var legToPtX = inLegToPt.x - inVertex.x, legToPtY = inLegToPt.y - inVertex.y; - var otherPtX = inOtherPt.x - inVertex.x, otherPtY = inOtherPt.y - inVertex.y; - - // main angle >0: < 180 deg.; 0: 180 deg.; <0: > 180 deg. - var from2toAngle = legFromPtX * legToPtY - legFromPtY * legToPtX; - var from2otherAngle = legFromPtX * otherPtY - legFromPtY * otherPtX; - - if ( Math.abs( from2toAngle ) > Number.EPSILON ) { - - // angle != 180 deg. - - var other2toAngle = otherPtX * legToPtY - otherPtY * legToPtX; - // console.log( "from2to: " + from2toAngle + ", from2other: " + from2otherAngle + ", other2to: " + other2toAngle ); - - if ( from2toAngle > 0 ) { - - // main angle < 180 deg. - return ( ( from2otherAngle >= 0 ) && ( other2toAngle >= 0 ) ); - - } else { - - // main angle > 180 deg. - return ( ( from2otherAngle >= 0 ) || ( other2toAngle >= 0 ) ); - - } - - } else { - - // angle == 180 deg. - // console.log( "from2to: 180 deg., from2other: " + from2otherAngle ); - return ( from2otherAngle > 0 ); - - } - - } - - - function removeHoles( contour, holes ) { - - var shape = contour.concat(); // work on this shape - var hole; - - function isCutLineInsideAngles( inShapeIdx, inHoleIdx ) { - - // Check if hole point lies within angle around shape point - var lastShapeIdx = shape.length - 1; - - var prevShapeIdx = inShapeIdx - 1; - if ( prevShapeIdx < 0 ) prevShapeIdx = lastShapeIdx; - - var nextShapeIdx = inShapeIdx + 1; - if ( nextShapeIdx > lastShapeIdx ) nextShapeIdx = 0; - - var insideAngle = isPointInsideAngle( shape[ inShapeIdx ], shape[ prevShapeIdx ], shape[ nextShapeIdx ], hole[ inHoleIdx ] ); - if ( ! insideAngle ) { - - // console.log( "Vertex (Shape): " + inShapeIdx + ", Point: " + hole[inHoleIdx].x + "/" + hole[inHoleIdx].y ); - return false; - - } - - // Check if shape point lies within angle around hole point - var lastHoleIdx = hole.length - 1; - - var prevHoleIdx = inHoleIdx - 1; - if ( prevHoleIdx < 0 ) prevHoleIdx = lastHoleIdx; - - var nextHoleIdx = inHoleIdx + 1; - if ( nextHoleIdx > lastHoleIdx ) nextHoleIdx = 0; - - insideAngle = isPointInsideAngle( hole[ inHoleIdx ], hole[ prevHoleIdx ], hole[ nextHoleIdx ], shape[ inShapeIdx ] ); - if ( ! insideAngle ) { - - // console.log( "Vertex (Hole): " + inHoleIdx + ", Point: " + shape[inShapeIdx].x + "/" + shape[inShapeIdx].y ); - return false; - - } - - return true; - - } - - function intersectsShapeEdge( inShapePt, inHolePt ) { - - // checks for intersections with shape edges - var sIdx, nextIdx, intersection; - for ( sIdx = 0; sIdx < shape.length; sIdx ++ ) { - - nextIdx = sIdx + 1; nextIdx %= shape.length; - intersection = intersect_segments_2D( inShapePt, inHolePt, shape[ sIdx ], shape[ nextIdx ], true ); - if ( intersection.length > 0 ) return true; - - } - - return false; - - } - - var indepHoles = []; - - function intersectsHoleEdge( inShapePt, inHolePt ) { - - // checks for intersections with hole edges - var ihIdx, chkHole, - hIdx, nextIdx, intersection; - for ( ihIdx = 0; ihIdx < indepHoles.length; ihIdx ++ ) { - - chkHole = holes[ indepHoles[ ihIdx ] ]; - for ( hIdx = 0; hIdx < chkHole.length; hIdx ++ ) { - - nextIdx = hIdx + 1; nextIdx %= chkHole.length; - intersection = intersect_segments_2D( inShapePt, inHolePt, chkHole[ hIdx ], chkHole[ nextIdx ], true ); - if ( intersection.length > 0 ) return true; - - } - - } - return false; - - } - - var holeIndex, shapeIndex, - shapePt, holePt, - holeIdx, cutKey, failedCuts = [], - tmpShape1, tmpShape2, - tmpHole1, tmpHole2; - - for ( var h = 0, hl = holes.length; h < hl; h ++ ) { - - indepHoles.push( h ); - - } - - var minShapeIndex = 0; - var counter = indepHoles.length * 2; - while ( indepHoles.length > 0 ) { - - counter --; - if ( counter < 0 ) { - - console.log( 'THREE.ShapeUtils: Infinite Loop! Holes left:" + indepHoles.length + ", Probably Hole outside Shape!' ); - break; - - } - - // search for shape-vertex and hole-vertex, - // which can be connected without intersections - for ( shapeIndex = minShapeIndex; shapeIndex < shape.length; shapeIndex ++ ) { - - shapePt = shape[ shapeIndex ]; - holeIndex = - 1; - - // search for hole which can be reached without intersections - for ( var h = 0; h < indepHoles.length; h ++ ) { - - holeIdx = indepHoles[ h ]; - - // prevent multiple checks - cutKey = shapePt.x + ':' + shapePt.y + ':' + holeIdx; - if ( failedCuts[ cutKey ] !== undefined ) continue; - - hole = holes[ holeIdx ]; - for ( var h2 = 0; h2 < hole.length; h2 ++ ) { - - holePt = hole[ h2 ]; - if ( ! isCutLineInsideAngles( shapeIndex, h2 ) ) continue; - if ( intersectsShapeEdge( shapePt, holePt ) ) continue; - if ( intersectsHoleEdge( shapePt, holePt ) ) continue; - - holeIndex = h2; - indepHoles.splice( h, 1 ); - - tmpShape1 = shape.slice( 0, shapeIndex + 1 ); - tmpShape2 = shape.slice( shapeIndex ); - tmpHole1 = hole.slice( holeIndex ); - tmpHole2 = hole.slice( 0, holeIndex + 1 ); - - shape = tmpShape1.concat( tmpHole1 ).concat( tmpHole2 ).concat( tmpShape2 ); - - minShapeIndex = shapeIndex; - - // Debug only, to show the selected cuts - // glob_CutLines.push( [ shapePt, holePt ] ); - - break; - - } - if ( holeIndex >= 0 ) break; // hole-vertex found - - failedCuts[ cutKey ] = true; // remember failure - - } - if ( holeIndex >= 0 ) break; // hole-vertex found - - } - - } - - return shape; /* shape with no holes */ - - } - - - var i, il, f, face, - key, index, - allPointsMap = {}; - - // To maintain reference to old shape, one must match coordinates, or offset the indices from original arrays. It's probably easier to do the first. - - var allpoints = contour.concat(); - - for ( var h = 0, hl = holes.length; h < hl; h ++ ) { - - Array.prototype.push.apply( allpoints, holes[ h ] ); - - } - - //console.log( "allpoints",allpoints, allpoints.length ); - - // prepare all points map - - for ( i = 0, il = allpoints.length; i < il; i ++ ) { - - key = allpoints[ i ].x + ':' + allpoints[ i ].y; - - if ( allPointsMap[ key ] !== undefined ) { - - console.warn( 'THREE.ShapeUtils: Duplicate point', key, i ); - - } - - allPointsMap[ key ] = i; - - } - - // remove holes by cutting paths to holes and adding them to the shape - var shapeWithoutHoles = removeHoles( contour, holes ); - - var triangles = ShapeUtils.triangulate( shapeWithoutHoles, false ); // True returns indices for points of spooled shape - //console.log( "triangles",triangles, triangles.length ); - - // check all face vertices against all points map - - for ( i = 0, il = triangles.length; i < il; i ++ ) { - - face = triangles[ i ]; - - for ( f = 0; f < 3; f ++ ) { - - key = face[ f ].x + ':' + face[ f ].y; - - index = allPointsMap[ key ]; - - if ( index !== undefined ) { - - face[ f ] = index; - - } - - } - - } - - return triangles.concat(); - - }, - - isClockWise: function ( pts ) { - - return ShapeUtils.area( pts ) < 0; - - } - -}; - -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - * - * Creates extruded geometry from a path shape. - * - * parameters = { - * - * curveSegments: , // number of points on the curves - * steps: , // number of points for z-side extrusions / used for subdividing segments of extrude spline too - * amount: , // Depth to extrude the shape - * - * bevelEnabled: , // turn on bevel - * bevelThickness: , // how deep into the original shape bevel goes - * bevelSize: , // how far from shape outline is bevel - * bevelSegments: , // number of bevel layers - * - * extrudePath: // curve to extrude shape along - * frames: // containing arrays of tangents, normals, binormals - * - * UVGenerator: // object that provides UV generator functions - * - * } - */ - -// ExtrudeGeometry - -function ExtrudeGeometry( shapes, options ) { - - Geometry.call( this ); - - this.type = 'ExtrudeGeometry'; - - this.parameters = { - shapes: shapes, - options: options - }; - - this.fromBufferGeometry( new ExtrudeBufferGeometry( shapes, options ) ); - this.mergeVertices(); - -} - -ExtrudeGeometry.prototype = Object.create( Geometry.prototype ); -ExtrudeGeometry.prototype.constructor = ExtrudeGeometry; - -// ExtrudeBufferGeometry - -function ExtrudeBufferGeometry( shapes, options ) { - - if ( typeof ( shapes ) === "undefined" ) { - - return; - - } - - BufferGeometry.call( this ); - - this.type = 'ExtrudeBufferGeometry'; - - shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; - - this.addShapeList( shapes, options ); - - this.computeVertexNormals(); - - // can't really use automatic vertex normals - // as then front and back sides get smoothed too - // should do separate smoothing just for sides - - //this.computeVertexNormals(); - - //console.log( "took", ( Date.now() - startTime ) ); - -} - -ExtrudeBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -ExtrudeBufferGeometry.prototype.constructor = ExtrudeBufferGeometry; - -ExtrudeBufferGeometry.prototype.getArrays = function () { - - var positionAttribute = this.getAttribute( "position" ); - var verticesArray = positionAttribute ? Array.prototype.slice.call( positionAttribute.array ) : []; - - var uvAttribute = this.getAttribute( "uv" ); - var uvArray = uvAttribute ? Array.prototype.slice.call( uvAttribute.array ) : []; - - var IndexAttribute = this.index; - var indicesArray = IndexAttribute ? Array.prototype.slice.call( IndexAttribute.array ) : []; - - return { - position: verticesArray, - uv: uvArray, - index: indicesArray - }; - -}; - -ExtrudeBufferGeometry.prototype.addShapeList = function ( shapes, options ) { - - var sl = shapes.length; - options.arrays = this.getArrays(); - - for ( var s = 0; s < sl; s ++ ) { - - var shape = shapes[ s ]; - this.addShape( shape, options ); - - } - - this.setIndex( options.arrays.index ); - this.addAttribute( 'position', new Float32BufferAttribute( options.arrays.position, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( options.arrays.uv, 2 ) ); - -}; - -ExtrudeBufferGeometry.prototype.addShape = function ( shape, options ) { - - var arrays = options.arrays ? options.arrays : this.getArrays(); - var verticesArray = arrays.position; - var indicesArray = arrays.index; - var uvArray = arrays.uv; - - var placeholder = []; - - - var amount = options.amount !== undefined ? options.amount : 100; - - var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6; // 10 - var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2; // 8 - var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; - - var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false - - var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; - - var steps = options.steps !== undefined ? options.steps : 1; - - var extrudePath = options.extrudePath; - var extrudePts, extrudeByPath = false; - - // Use default WorldUVGenerator if no UV generators are specified. - var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : ExtrudeGeometry.WorldUVGenerator; - - var splineTube, binormal, normal, position2; - if ( extrudePath ) { - - extrudePts = extrudePath.getSpacedPoints( steps ); - - extrudeByPath = true; - bevelEnabled = false; // bevels not supported for path extrusion - - // SETUP TNB variables - - // TODO1 - have a .isClosed in spline? - - splineTube = options.frames !== undefined ? options.frames : extrudePath.computeFrenetFrames( steps, false ); - - // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); - - binormal = new Vector3(); - normal = new Vector3(); - position2 = new Vector3(); - - } - - // Safeguards if bevels are not enabled - - if ( ! bevelEnabled ) { - - bevelSegments = 0; - bevelThickness = 0; - bevelSize = 0; - - } - - // Variables initialization - - var ahole, h, hl; // looping of holes - var scope = this; - - var shapePoints = shape.extractPoints( curveSegments ); - - var vertices = shapePoints.shape; - var holes = shapePoints.holes; - - var reverse = ! ShapeUtils.isClockWise( vertices ); - - if ( reverse ) { - - vertices = vertices.reverse(); - - // Maybe we should also check if holes are in the opposite direction, just to be safe ... - - for ( h = 0, hl = holes.length; h < hl; h ++ ) { - - ahole = holes[ h ]; - - if ( ShapeUtils.isClockWise( ahole ) ) { - - holes[ h ] = ahole.reverse(); - - } - - } - - } - - - var faces = ShapeUtils.triangulateShape( vertices, holes ); - - /* Vertices */ - - var contour = vertices; // vertices has all points but contour has only points of circumference - - for ( h = 0, hl = holes.length; h < hl; h ++ ) { - - ahole = holes[ h ]; - - vertices = vertices.concat( ahole ); - - } - - - function scalePt2( pt, vec, size ) { - - if ( ! vec ) console.error( "THREE.ExtrudeGeometry: vec does not exist" ); - - return vec.clone().multiplyScalar( size ).add( pt ); - - } - - var b, bs, t, z, - vert, vlen = vertices.length, - face, flen = faces.length; - - - // Find directions for point movement - - - function getBevelVec( inPt, inPrev, inNext ) { - - // computes for inPt the corresponding point inPt' on a new contour - // shifted by 1 unit (length of normalized vector) to the left - // if we walk along contour clockwise, this new contour is outside the old one - // - // inPt' is the intersection of the two lines parallel to the two - // adjacent edges of inPt at a distance of 1 unit on the left side. - - var v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt - - // good reading for geometry algorithms (here: line-line intersection) - // http://geomalgorithms.com/a05-_intersect-1.html - - var v_prev_x = inPt.x - inPrev.x, - v_prev_y = inPt.y - inPrev.y; - var v_next_x = inNext.x - inPt.x, - v_next_y = inNext.y - inPt.y; - - var v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); - - // check for collinear edges - var collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); - - if ( Math.abs( collinear0 ) > Number.EPSILON ) { - - // not collinear - - // length of vectors for normalizing - - var v_prev_len = Math.sqrt( v_prev_lensq ); - var v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); - - // shift adjacent points by unit vectors to the left - - var ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); - var ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); - - var ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); - var ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); - - // scaling factor for v_prev to intersection point - - var sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - - ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / - ( v_prev_x * v_next_y - v_prev_y * v_next_x ); - - // vector from inPt to intersection point - - v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); - v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); - - // Don't normalize!, otherwise sharp corners become ugly - // but prevent crazy spikes - var v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); - if ( v_trans_lensq <= 2 ) { - - return new Vector2( v_trans_x, v_trans_y ); - - } else { - - shrink_by = Math.sqrt( v_trans_lensq / 2 ); - - } - - } else { - - // handle special case of collinear edges - - var direction_eq = false; // assumes: opposite - if ( v_prev_x > Number.EPSILON ) { - - if ( v_next_x > Number.EPSILON ) { - - direction_eq = true; - - } - - } else { - - if ( v_prev_x < - Number.EPSILON ) { - - if ( v_next_x < - Number.EPSILON ) { - - direction_eq = true; - - } - - } else { - - if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { - - direction_eq = true; - - } - - } - - } - - if ( direction_eq ) { - - // console.log("Warning: lines are a straight sequence"); - v_trans_x = - v_prev_y; - v_trans_y = v_prev_x; - shrink_by = Math.sqrt( v_prev_lensq ); - - } else { - - // console.log("Warning: lines are a straight spike"); - v_trans_x = v_prev_x; - v_trans_y = v_prev_y; - shrink_by = Math.sqrt( v_prev_lensq / 2 ); - - } - - } - - return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); - - } - - - var contourMovements = []; - - for ( var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - - if ( j === il ) j = 0; - if ( k === il ) k = 0; - - // (j)---(i)---(k) - // console.log('i,j,k', i, j , k) - - contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); - - } - - var holesMovements = [], - oneHoleMovements, verticesMovements = contourMovements.concat(); - - for ( h = 0, hl = holes.length; h < hl; h ++ ) { - - ahole = holes[ h ]; - - oneHoleMovements = []; - - for ( i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { - - if ( j === il ) j = 0; - if ( k === il ) k = 0; - - // (j)---(i)---(k) - oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); - - } - - holesMovements.push( oneHoleMovements ); - verticesMovements = verticesMovements.concat( oneHoleMovements ); - - } - - - // Loop bevelSegments, 1 for the front, 1 for the back - - for ( b = 0; b < bevelSegments; b ++ ) { - - //for ( b = bevelSegments; b > 0; b -- ) { - - t = b / bevelSegments; - z = bevelThickness * Math.cos( t * Math.PI / 2 ); - bs = bevelSize * Math.sin( t * Math.PI / 2 ); - - // contract shape - - for ( i = 0, il = contour.length; i < il; i ++ ) { - - vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - - v( vert.x, vert.y, - z ); - - } - - // expand holes - - for ( h = 0, hl = holes.length; h < hl; h ++ ) { - - ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; - - for ( i = 0, il = ahole.length; i < il; i ++ ) { - - vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - - v( vert.x, vert.y, - z ); - - } - - } - - } - - bs = bevelSize; - - // Back facing vertices - - for ( i = 0; i < vlen; i ++ ) { - - vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - - if ( ! extrudeByPath ) { - - v( vert.x, vert.y, 0 ); - - } else { - - // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); - - normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); - - position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); - - v( position2.x, position2.y, position2.z ); - - } - - } - - // Add stepped vertices... - // Including front facing vertices - - var s; - - for ( s = 1; s <= steps; s ++ ) { - - for ( i = 0; i < vlen; i ++ ) { - - vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; - - if ( ! extrudeByPath ) { - - v( vert.x, vert.y, amount / steps * s ); - - } else { - - // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); - - normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); - binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); - - position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); - - v( position2.x, position2.y, position2.z ); - - } - - } - - } - - - // Add bevel segments planes - - //for ( b = 1; b <= bevelSegments; b ++ ) { - for ( b = bevelSegments - 1; b >= 0; b -- ) { - - t = b / bevelSegments; - z = bevelThickness * Math.cos( t * Math.PI / 2 ); - bs = bevelSize * Math.sin( t * Math.PI / 2 ); - - // contract shape - - for ( i = 0, il = contour.length; i < il; i ++ ) { - - vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); - v( vert.x, vert.y, amount + z ); - - } - - // expand holes - - for ( h = 0, hl = holes.length; h < hl; h ++ ) { - - ahole = holes[ h ]; - oneHoleMovements = holesMovements[ h ]; - - for ( i = 0, il = ahole.length; i < il; i ++ ) { - - vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); - - if ( ! extrudeByPath ) { - - v( vert.x, vert.y, amount + z ); - - } else { - - v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); - - } - - } - - } - - } - - /* Faces */ - - // Top and bottom faces - - buildLidFaces(); - - // Sides faces - - buildSideFaces(); - - - ///// Internal functions - - function buildLidFaces() { - - var start = verticesArray.length / 3; - - if ( bevelEnabled ) { - - var layer = 0; // steps + 1 - var offset = vlen * layer; - - // Bottom faces - - for ( i = 0; i < flen; i ++ ) { - - face = faces[ i ]; - f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); - - } - - layer = steps + bevelSegments * 2; - offset = vlen * layer; - - // Top faces - - for ( i = 0; i < flen; i ++ ) { - - face = faces[ i ]; - f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); - - } - - } else { - - // Bottom faces - - for ( i = 0; i < flen; i ++ ) { - - face = faces[ i ]; - f3( face[ 2 ], face[ 1 ], face[ 0 ] ); - - } - - // Top faces - - for ( i = 0; i < flen; i ++ ) { - - face = faces[ i ]; - f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); - - } - - } - - scope.addGroup( start, verticesArray.length / 3 - start, options.material !== undefined ? options.material : 0 ); - - } - - // Create faces for the z-sides of the shape - - function buildSideFaces() { - - var start = verticesArray.length / 3; - var layeroffset = 0; - sidewalls( contour, layeroffset ); - layeroffset += contour.length; - - for ( h = 0, hl = holes.length; h < hl; h ++ ) { - - ahole = holes[ h ]; - sidewalls( ahole, layeroffset ); - - //, true - layeroffset += ahole.length; - - } - - - scope.addGroup( start, verticesArray.length / 3 - start, options.extrudeMaterial !== undefined ? options.extrudeMaterial : 1 ); - - - } - - function sidewalls( contour, layeroffset ) { - - var j, k; - i = contour.length; - - while ( -- i >= 0 ) { - - j = i; - k = i - 1; - if ( k < 0 ) k = contour.length - 1; - - //console.log('b', i,j, i-1, k,vertices.length); - - var s = 0, - sl = steps + bevelSegments * 2; - - for ( s = 0; s < sl; s ++ ) { - - var slen1 = vlen * s; - var slen2 = vlen * ( s + 1 ); - - var a = layeroffset + j + slen1, - b = layeroffset + k + slen1, - c = layeroffset + k + slen2, - d = layeroffset + j + slen2; - - f4( a, b, c, d ); - - } - - } - - } - - function v( x, y, z ) { - - placeholder.push( x ); - placeholder.push( y ); - placeholder.push( z ); - - } - - - function f3( a, b, c ) { - - addVertex( a ); - addVertex( b ); - addVertex( c ); - - var nextIndex = verticesArray.length / 3; - var uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); - - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); - - } - - function f4( a, b, c, d ) { - - addVertex( a ); - addVertex( b ); - addVertex( d ); - - addVertex( b ); - addVertex( c ); - addVertex( d ); - - - var nextIndex = verticesArray.length / 3; - var uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); - - addUV( uvs[ 0 ] ); - addUV( uvs[ 1 ] ); - addUV( uvs[ 3 ] ); - - addUV( uvs[ 1 ] ); - addUV( uvs[ 2 ] ); - addUV( uvs[ 3 ] ); - - } - - function addVertex( index ) { - - indicesArray.push( verticesArray.length / 3 ); - verticesArray.push( placeholder[ index * 3 + 0 ] ); - verticesArray.push( placeholder[ index * 3 + 1 ] ); - verticesArray.push( placeholder[ index * 3 + 2 ] ); - - } - - - function addUV( vector2 ) { - - uvArray.push( vector2.x ); - uvArray.push( vector2.y ); - - } - - if ( ! options.arrays ) { - - this.setIndex( indicesArray ); - this.addAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( options.arrays.uv, 2 ) ); - - } - -}; - -ExtrudeGeometry.WorldUVGenerator = { - - generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { - - var a_x = vertices[ indexA * 3 ]; - var a_y = vertices[ indexA * 3 + 1 ]; - var b_x = vertices[ indexB * 3 ]; - var b_y = vertices[ indexB * 3 + 1 ]; - var c_x = vertices[ indexC * 3 ]; - var c_y = vertices[ indexC * 3 + 1 ]; - - return [ - new Vector2( a_x, a_y ), - new Vector2( b_x, b_y ), - new Vector2( c_x, c_y ) - ]; - - }, - - generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { - - var a_x = vertices[ indexA * 3 ]; - var a_y = vertices[ indexA * 3 + 1 ]; - var a_z = vertices[ indexA * 3 + 2 ]; - var b_x = vertices[ indexB * 3 ]; - var b_y = vertices[ indexB * 3 + 1 ]; - var b_z = vertices[ indexB * 3 + 2 ]; - var c_x = vertices[ indexC * 3 ]; - var c_y = vertices[ indexC * 3 + 1 ]; - var c_z = vertices[ indexC * 3 + 2 ]; - var d_x = vertices[ indexD * 3 ]; - var d_y = vertices[ indexD * 3 + 1 ]; - var d_z = vertices[ indexD * 3 + 2 ]; - - if ( Math.abs( a_y - b_y ) < 0.01 ) { - - return [ - new Vector2( a_x, 1 - a_z ), - new Vector2( b_x, 1 - b_z ), - new Vector2( c_x, 1 - c_z ), - new Vector2( d_x, 1 - d_z ) - ]; - - } else { - - return [ - new Vector2( a_y, 1 - a_z ), - new Vector2( b_y, 1 - b_z ), - new Vector2( c_y, 1 - c_z ), - new Vector2( d_y, 1 - d_z ) - ]; - - } - - } -}; - -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - * @author alteredq / http://alteredqualia.com/ - * - * Text = 3D Text - * - * parameters = { - * font: , // font - * - * size: , // size of the text - * height: , // thickness to extrude text - * curveSegments: , // number of points on the curves - * - * bevelEnabled: , // turn on bevel - * bevelThickness: , // how deep into text bevel goes - * bevelSize: // how far from text outline is bevel - * } - */ - -// TextGeometry - -function TextGeometry( text, parameters ) { - - Geometry.call( this ); - - this.type = 'TextGeometry'; - - this.parameters = { - text: text, - parameters: parameters - }; - - this.fromBufferGeometry( new TextBufferGeometry( text, parameters ) ); - this.mergeVertices(); - -} - -TextGeometry.prototype = Object.create( Geometry.prototype ); -TextGeometry.prototype.constructor = TextGeometry; - -// TextBufferGeometry - -function TextBufferGeometry( text, parameters ) { - - parameters = parameters || {}; - - var font = parameters.font; - - if ( ! ( font && font.isFont ) ) { - - console.error( 'THREE.TextGeometry: font parameter is not an instance of THREE.Font.' ); - return new Geometry(); - - } - - var shapes = font.generateShapes( text, parameters.size, parameters.curveSegments ); - - // translate parameters to ExtrudeGeometry API - - parameters.amount = parameters.height !== undefined ? parameters.height : 50; - - // defaults - - if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; - if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; - if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; - - ExtrudeBufferGeometry.call( this, shapes, parameters ); - - this.type = 'TextBufferGeometry'; - -} - -TextBufferGeometry.prototype = Object.create( ExtrudeBufferGeometry.prototype ); -TextBufferGeometry.prototype.constructor = TextBufferGeometry; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author benaadams / https://twitter.com/ben_a_adams - * @author Mugen87 / https://github.com/Mugen87 - */ - -// SphereGeometry - -function SphereGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { - - Geometry.call( this ); - - this.type = 'SphereGeometry'; - - this.parameters = { - radius: radius, - widthSegments: widthSegments, - heightSegments: heightSegments, - phiStart: phiStart, - phiLength: phiLength, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - this.fromBufferGeometry( new SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) ); - this.mergeVertices(); - -} - -SphereGeometry.prototype = Object.create( Geometry.prototype ); -SphereGeometry.prototype.constructor = SphereGeometry; - -// SphereBufferGeometry - -function SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { - - BufferGeometry.call( this ); - - this.type = 'SphereBufferGeometry'; - - this.parameters = { - radius: radius, - widthSegments: widthSegments, - heightSegments: heightSegments, - phiStart: phiStart, - phiLength: phiLength, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - radius = radius || 1; - - widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 ); - heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 ); - - phiStart = phiStart !== undefined ? phiStart : 0; - phiLength = phiLength !== undefined ? phiLength : Math.PI * 2; - - thetaStart = thetaStart !== undefined ? thetaStart : 0; - thetaLength = thetaLength !== undefined ? thetaLength : Math.PI; - - var thetaEnd = thetaStart + thetaLength; - - var ix, iy; - - var index = 0; - var grid = []; - - var vertex = new Vector3(); - var normal = new Vector3(); - - // buffers - - var indices = []; - var vertices = []; - var normals = []; - var uvs = []; - - // generate vertices, normals and uvs - - for ( iy = 0; iy <= heightSegments; iy ++ ) { - - var verticesRow = []; - - var v = iy / heightSegments; - - for ( ix = 0; ix <= widthSegments; ix ++ ) { - - var u = ix / widthSegments; - - // vertex - - vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); - vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); - vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normal.set( vertex.x, vertex.y, vertex.z ).normalize(); - normals.push( normal.x, normal.y, normal.z ); - - // uv - - uvs.push( u, 1 - v ); - - verticesRow.push( index ++ ); - - } - - grid.push( verticesRow ); - - } - - // indices - - for ( iy = 0; iy < heightSegments; iy ++ ) { - - for ( ix = 0; ix < widthSegments; ix ++ ) { - - var a = grid[ iy ][ ix + 1 ]; - var b = grid[ iy ][ ix ]; - var c = grid[ iy + 1 ][ ix ]; - var d = grid[ iy + 1 ][ ix + 1 ]; - - if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); - if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - -} - -SphereBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -SphereBufferGeometry.prototype.constructor = SphereBufferGeometry; - -/** - * @author Kaleb Murphy - * @author Mugen87 / https://github.com/Mugen87 - */ - -// RingGeometry - -function RingGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) { - - Geometry.call( this ); - - this.type = 'RingGeometry'; - - this.parameters = { - innerRadius: innerRadius, - outerRadius: outerRadius, - thetaSegments: thetaSegments, - phiSegments: phiSegments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - this.fromBufferGeometry( new RingBufferGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) ); - this.mergeVertices(); - -} - -RingGeometry.prototype = Object.create( Geometry.prototype ); -RingGeometry.prototype.constructor = RingGeometry; - -// RingBufferGeometry - -function RingBufferGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) { - - BufferGeometry.call( this ); - - this.type = 'RingBufferGeometry'; - - this.parameters = { - innerRadius: innerRadius, - outerRadius: outerRadius, - thetaSegments: thetaSegments, - phiSegments: phiSegments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - innerRadius = innerRadius || 0.5; - outerRadius = outerRadius || 1; - - thetaStart = thetaStart !== undefined ? thetaStart : 0; - thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; - - thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8; - phiSegments = phiSegments !== undefined ? Math.max( 1, phiSegments ) : 1; - - // buffers - - var indices = []; - var vertices = []; - var normals = []; - var uvs = []; - - // some helper variables - - var segment; - var radius = innerRadius; - var radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); - var vertex = new Vector3(); - var uv = new Vector2(); - var j, i; - - // generate vertices, normals and uvs - - for ( j = 0; j <= phiSegments; j ++ ) { - - for ( i = 0; i <= thetaSegments; i ++ ) { - - // values are generate from the inside of the ring to the outside - - segment = thetaStart + i / thetaSegments * thetaLength; - - // vertex - - vertex.x = radius * Math.cos( segment ); - vertex.y = radius * Math.sin( segment ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normals.push( 0, 0, 1 ); - - // uv - - uv.x = ( vertex.x / outerRadius + 1 ) / 2; - uv.y = ( vertex.y / outerRadius + 1 ) / 2; - - uvs.push( uv.x, uv.y ); - - } - - // increase the radius for next row of vertices - - radius += radiusStep; - - } - - // indices - - for ( j = 0; j < phiSegments; j ++ ) { - - var thetaSegmentLevel = j * ( thetaSegments + 1 ); - - for ( i = 0; i < thetaSegments; i ++ ) { - - segment = i + thetaSegmentLevel; - - var a = segment; - var b = segment + thetaSegments + 1; - var c = segment + thetaSegments + 2; - var d = segment + 1; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - -} - -RingBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -RingBufferGeometry.prototype.constructor = RingBufferGeometry; - -/** - * @author astrodud / http://astrodud.isgreat.org/ - * @author zz85 / https://github.com/zz85 - * @author bhouston / http://clara.io - * @author Mugen87 / https://github.com/Mugen87 - */ - -// LatheGeometry - -function LatheGeometry( points, segments, phiStart, phiLength ) { - - Geometry.call( this ); - - this.type = 'LatheGeometry'; - - this.parameters = { - points: points, - segments: segments, - phiStart: phiStart, - phiLength: phiLength - }; - - this.fromBufferGeometry( new LatheBufferGeometry( points, segments, phiStart, phiLength ) ); - this.mergeVertices(); - -} - -LatheGeometry.prototype = Object.create( Geometry.prototype ); -LatheGeometry.prototype.constructor = LatheGeometry; - -// LatheBufferGeometry - -function LatheBufferGeometry( points, segments, phiStart, phiLength ) { - - BufferGeometry.call( this ); - - this.type = 'LatheBufferGeometry'; - - this.parameters = { - points: points, - segments: segments, - phiStart: phiStart, - phiLength: phiLength - }; - - segments = Math.floor( segments ) || 12; - phiStart = phiStart || 0; - phiLength = phiLength || Math.PI * 2; - - // clamp phiLength so it's in range of [ 0, 2PI ] - - phiLength = _Math.clamp( phiLength, 0, Math.PI * 2 ); - - - // buffers - - var indices = []; - var vertices = []; - var uvs = []; - - // helper variables - - var base; - var inverseSegments = 1.0 / segments; - var vertex = new Vector3(); - var uv = new Vector2(); - var i, j; - - // generate vertices and uvs - - for ( i = 0; i <= segments; i ++ ) { - - var phi = phiStart + i * inverseSegments * phiLength; - - var sin = Math.sin( phi ); - var cos = Math.cos( phi ); - - for ( j = 0; j <= ( points.length - 1 ); j ++ ) { - - // vertex - - vertex.x = points[ j ].x * sin; - vertex.y = points[ j ].y; - vertex.z = points[ j ].x * cos; - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // uv - - uv.x = i / segments; - uv.y = j / ( points.length - 1 ); - - uvs.push( uv.x, uv.y ); - - - } - - } - - // indices - - for ( i = 0; i < segments; i ++ ) { - - for ( j = 0; j < ( points.length - 1 ); j ++ ) { - - base = j + i * points.length; - - var a = base; - var b = base + points.length; - var c = base + points.length + 1; - var d = base + 1; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - } - - } - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - // generate normals - - this.computeVertexNormals(); - - // if the geometry is closed, we need to average the normals along the seam. - // because the corresponding vertices are identical (but still have different UVs). - - if ( phiLength === Math.PI * 2 ) { - - var normals = this.attributes.normal.array; - var n1 = new Vector3(); - var n2 = new Vector3(); - var n = new Vector3(); - - // this is the buffer offset for the last line of vertices - - base = segments * points.length * 3; - - for ( i = 0, j = 0; i < points.length; i ++, j += 3 ) { - - // select the normal of the vertex in the first line - - n1.x = normals[ j + 0 ]; - n1.y = normals[ j + 1 ]; - n1.z = normals[ j + 2 ]; - - // select the normal of the vertex in the last line - - n2.x = normals[ base + j + 0 ]; - n2.y = normals[ base + j + 1 ]; - n2.z = normals[ base + j + 2 ]; - - // average normals - - n.addVectors( n1, n2 ).normalize(); - - // assign the new values to both normals - - normals[ j + 0 ] = normals[ base + j + 0 ] = n.x; - normals[ j + 1 ] = normals[ base + j + 1 ] = n.y; - normals[ j + 2 ] = normals[ base + j + 2 ] = n.z; - - } - - } - -} - -LatheBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -LatheBufferGeometry.prototype.constructor = LatheBufferGeometry; - -/** - * @author jonobr1 / http://jonobr1.com - * @author Mugen87 / https://github.com/Mugen87 - */ - -// ShapeGeometry - -function ShapeGeometry( shapes, curveSegments ) { - - Geometry.call( this ); - - this.type = 'ShapeGeometry'; - - if ( typeof curveSegments === 'object' ) { - - console.warn( 'THREE.ShapeGeometry: Options parameter has been removed.' ); - - curveSegments = curveSegments.curveSegments; - - } - - this.parameters = { - shapes: shapes, - curveSegments: curveSegments - }; - - this.fromBufferGeometry( new ShapeBufferGeometry( shapes, curveSegments ) ); - this.mergeVertices(); - -} - -ShapeGeometry.prototype = Object.create( Geometry.prototype ); -ShapeGeometry.prototype.constructor = ShapeGeometry; - -// ShapeBufferGeometry - -function ShapeBufferGeometry( shapes, curveSegments ) { - - BufferGeometry.call( this ); - - this.type = 'ShapeBufferGeometry'; - - this.parameters = { - shapes: shapes, - curveSegments: curveSegments - }; - - curveSegments = curveSegments || 12; - - // buffers - - var indices = []; - var vertices = []; - var normals = []; - var uvs = []; - - // helper variables - - var groupStart = 0; - var groupCount = 0; - - // allow single and array values for "shapes" parameter - - if ( Array.isArray( shapes ) === false ) { - - addShape( shapes ); - - } else { - - for ( var i = 0; i < shapes.length; i ++ ) { - - addShape( shapes[ i ] ); - - this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support - - groupStart += groupCount; - groupCount = 0; - - } - - } - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - - // helper functions - - function addShape( shape ) { - - var i, l, shapeHole; - - var indexOffset = vertices.length / 3; - var points = shape.extractPoints( curveSegments ); - - var shapeVertices = points.shape; - var shapeHoles = points.holes; - - // check direction of vertices - - if ( ShapeUtils.isClockWise( shapeVertices ) === false ) { - - shapeVertices = shapeVertices.reverse(); - - // also check if holes are in the opposite direction - - for ( i = 0, l = shapeHoles.length; i < l; i ++ ) { - - shapeHole = shapeHoles[ i ]; - - if ( ShapeUtils.isClockWise( shapeHole ) === true ) { - - shapeHoles[ i ] = shapeHole.reverse(); - - } - - } - - } - - var faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles ); - - // join vertices of inner and outer paths to a single array - - for ( i = 0, l = shapeHoles.length; i < l; i ++ ) { - - shapeHole = shapeHoles[ i ]; - shapeVertices = shapeVertices.concat( shapeHole ); - - } - - // vertices, normals, uvs - - for ( i = 0, l = shapeVertices.length; i < l; i ++ ) { - - var vertex = shapeVertices[ i ]; - - vertices.push( vertex.x, vertex.y, 0 ); - normals.push( 0, 0, 1 ); - uvs.push( vertex.x, vertex.y ); // world uvs - - } - - // incides - - for ( i = 0, l = faces.length; i < l; i ++ ) { - - var face = faces[ i ]; - - var a = face[ 0 ] + indexOffset; - var b = face[ 1 ] + indexOffset; - var c = face[ 2 ] + indexOffset; - - indices.push( a, b, c ); - groupCount += 3; - - } - - } - -} - -ShapeBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -ShapeBufferGeometry.prototype.constructor = ShapeBufferGeometry; - -/** - * @author WestLangley / http://github.com/WestLangley - * @author Mugen87 / https://github.com/Mugen87 - */ - -function EdgesGeometry( geometry, thresholdAngle ) { - - BufferGeometry.call( this ); - - this.type = 'EdgesGeometry'; - - this.parameters = { - thresholdAngle: thresholdAngle - }; - - thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1; - - // buffer - - var vertices = []; - - // helper variables - - var thresholdDot = Math.cos( _Math.DEG2RAD * thresholdAngle ); - var edge = [ 0, 0 ], edges = {}, edge1, edge2; - var key, keys = [ 'a', 'b', 'c' ]; - - // prepare source geometry - - var geometry2; - - if ( geometry.isBufferGeometry ) { - - geometry2 = new Geometry(); - geometry2.fromBufferGeometry( geometry ); - - } else { - - geometry2 = geometry.clone(); - - } - - geometry2.mergeVertices(); - geometry2.computeFaceNormals(); - - var sourceVertices = geometry2.vertices; - var faces = geometry2.faces; - - // now create a data structure where each entry represents an edge with its adjoining faces - - for ( var i = 0, l = faces.length; i < l; i ++ ) { - - var face = faces[ i ]; - - for ( var j = 0; j < 3; j ++ ) { - - edge1 = face[ keys[ j ] ]; - edge2 = face[ keys[ ( j + 1 ) % 3 ] ]; - edge[ 0 ] = Math.min( edge1, edge2 ); - edge[ 1 ] = Math.max( edge1, edge2 ); - - key = edge[ 0 ] + ',' + edge[ 1 ]; - - if ( edges[ key ] === undefined ) { - - edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ], face1: i, face2: undefined }; - - } else { - - edges[ key ].face2 = i; - - } - - } - - } - - // generate vertices - - for ( key in edges ) { - - var e = edges[ key ]; - - // an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree. - - if ( e.face2 === undefined || faces[ e.face1 ].normal.dot( faces[ e.face2 ].normal ) <= thresholdDot ) { - - var vertex = sourceVertices[ e.index1 ]; - vertices.push( vertex.x, vertex.y, vertex.z ); - - vertex = sourceVertices[ e.index2 ]; - vertices.push( vertex.x, vertex.y, vertex.z ); - - } - - } - - // build geometry - - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - -} - -EdgesGeometry.prototype = Object.create( BufferGeometry.prototype ); -EdgesGeometry.prototype.constructor = EdgesGeometry; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author Mugen87 / https://github.com/Mugen87 - */ - -// CylinderGeometry - -function CylinderGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { - - Geometry.call( this ); - - this.type = 'CylinderGeometry'; - - this.parameters = { - radiusTop: radiusTop, - radiusBottom: radiusBottom, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - this.fromBufferGeometry( new CylinderBufferGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) ); - this.mergeVertices(); - -} - -CylinderGeometry.prototype = Object.create( Geometry.prototype ); -CylinderGeometry.prototype.constructor = CylinderGeometry; - -// CylinderBufferGeometry - -function CylinderBufferGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { - - BufferGeometry.call( this ); - - this.type = 'CylinderBufferGeometry'; - - this.parameters = { - radiusTop: radiusTop, - radiusBottom: radiusBottom, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - var scope = this; - - radiusTop = radiusTop !== undefined ? radiusTop : 1; - radiusBottom = radiusBottom !== undefined ? radiusBottom : 1; - height = height || 1; - - radialSegments = Math.floor( radialSegments ) || 8; - heightSegments = Math.floor( heightSegments ) || 1; - - openEnded = openEnded !== undefined ? openEnded : false; - thetaStart = thetaStart !== undefined ? thetaStart : 0.0; - thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; - - // buffers - - var indices = []; - var vertices = []; - var normals = []; - var uvs = []; - - // helper variables - - var index = 0; - var indexArray = []; - var halfHeight = height / 2; - var groupStart = 0; - - // generate geometry - - generateTorso(); - - if ( openEnded === false ) { - - if ( radiusTop > 0 ) generateCap( true ); - if ( radiusBottom > 0 ) generateCap( false ); - - } - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - - function generateTorso() { - - var x, y; - var normal = new Vector3(); - var vertex = new Vector3(); - - var groupCount = 0; - - // this will be used to calculate the normal - var slope = ( radiusBottom - radiusTop ) / height; - - // generate vertices, normals and uvs - - for ( y = 0; y <= heightSegments; y ++ ) { - - var indexRow = []; - - var v = y / heightSegments; - - // calculate the radius of the current row - - var radius = v * ( radiusBottom - radiusTop ) + radiusTop; - - for ( x = 0; x <= radialSegments; x ++ ) { - - var u = x / radialSegments; - - var theta = u * thetaLength + thetaStart; - - var sinTheta = Math.sin( theta ); - var cosTheta = Math.cos( theta ); - - // vertex - - vertex.x = radius * sinTheta; - vertex.y = - v * height + halfHeight; - vertex.z = radius * cosTheta; - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normal.set( sinTheta, slope, cosTheta ).normalize(); - normals.push( normal.x, normal.y, normal.z ); - - // uv - - uvs.push( u, 1 - v ); - - // save index of vertex in respective row - - indexRow.push( index ++ ); - - } - - // now save vertices of the row in our index array - - indexArray.push( indexRow ); - - } - - // generate indices - - for ( x = 0; x < radialSegments; x ++ ) { - - for ( y = 0; y < heightSegments; y ++ ) { - - // we use the index array to access the correct indices - - var a = indexArray[ y ][ x ]; - var b = indexArray[ y + 1 ][ x ]; - var c = indexArray[ y + 1 ][ x + 1 ]; - var d = indexArray[ y ][ x + 1 ]; - - // faces - - indices.push( a, b, d ); - indices.push( b, c, d ); - - // update group counter - - groupCount += 6; - - } - - } - - // add a group to the geometry. this will ensure multi material support - - scope.addGroup( groupStart, groupCount, 0 ); - - // calculate new start value for groups - - groupStart += groupCount; - - } - - function generateCap( top ) { - - var x, centerIndexStart, centerIndexEnd; - - var uv = new Vector2(); - var vertex = new Vector3(); - - var groupCount = 0; - - var radius = ( top === true ) ? radiusTop : radiusBottom; - var sign = ( top === true ) ? 1 : - 1; - - // save the index of the first center vertex - centerIndexStart = index; - - // first we generate the center vertex data of the cap. - // because the geometry needs one set of uvs per face, - // we must generate a center vertex per face/segment - - for ( x = 1; x <= radialSegments; x ++ ) { - - // vertex - - vertices.push( 0, halfHeight * sign, 0 ); - - // normal - - normals.push( 0, sign, 0 ); - - // uv - - uvs.push( 0.5, 0.5 ); - - // increase index - - index ++; - - } - - // save the index of the last center vertex - - centerIndexEnd = index; - - // now we generate the surrounding vertices, normals and uvs - - for ( x = 0; x <= radialSegments; x ++ ) { - - var u = x / radialSegments; - var theta = u * thetaLength + thetaStart; - - var cosTheta = Math.cos( theta ); - var sinTheta = Math.sin( theta ); - - // vertex - - vertex.x = radius * sinTheta; - vertex.y = halfHeight * sign; - vertex.z = radius * cosTheta; - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normals.push( 0, sign, 0 ); - - // uv - - uv.x = ( cosTheta * 0.5 ) + 0.5; - uv.y = ( sinTheta * 0.5 * sign ) + 0.5; - uvs.push( uv.x, uv.y ); - - // increase index - - index ++; - - } - - // generate indices - - for ( x = 0; x < radialSegments; x ++ ) { - - var c = centerIndexStart + x; - var i = centerIndexEnd + x; - - if ( top === true ) { - - // face top - - indices.push( i, i + 1, c ); - - } else { - - // face bottom - - indices.push( i + 1, i, c ); - - } - - groupCount += 3; - - } - - // add a group to the geometry. this will ensure multi material support - - scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); - - // calculate new start value for groups - - groupStart += groupCount; - - } - -} - -CylinderBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -CylinderBufferGeometry.prototype.constructor = CylinderBufferGeometry; - -/** - * @author abelnation / http://github.com/abelnation - */ - -// ConeGeometry - -function ConeGeometry( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { - - CylinderGeometry.call( this, 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); - - this.type = 'ConeGeometry'; - - this.parameters = { - radius: radius, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - -} - -ConeGeometry.prototype = Object.create( CylinderGeometry.prototype ); -ConeGeometry.prototype.constructor = ConeGeometry; - -// ConeBufferGeometry - -function ConeBufferGeometry( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { - - CylinderBufferGeometry.call( this, 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); - - this.type = 'ConeBufferGeometry'; - - this.parameters = { - radius: radius, - height: height, - radialSegments: radialSegments, - heightSegments: heightSegments, - openEnded: openEnded, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - -} - -ConeBufferGeometry.prototype = Object.create( CylinderBufferGeometry.prototype ); -ConeBufferGeometry.prototype.constructor = ConeBufferGeometry; - -/** - * @author benaadams / https://twitter.com/ben_a_adams - * @author Mugen87 / https://github.com/Mugen87 - * @author hughes - */ - -// CircleGeometry - -function CircleGeometry( radius, segments, thetaStart, thetaLength ) { - - Geometry.call( this ); - - this.type = 'CircleGeometry'; - - this.parameters = { - radius: radius, - segments: segments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - this.fromBufferGeometry( new CircleBufferGeometry( radius, segments, thetaStart, thetaLength ) ); - this.mergeVertices(); - -} - -CircleGeometry.prototype = Object.create( Geometry.prototype ); -CircleGeometry.prototype.constructor = CircleGeometry; - -// CircleBufferGeometry - -function CircleBufferGeometry( radius, segments, thetaStart, thetaLength ) { - - BufferGeometry.call( this ); - - this.type = 'CircleBufferGeometry'; - - this.parameters = { - radius: radius, - segments: segments, - thetaStart: thetaStart, - thetaLength: thetaLength - }; - - radius = radius || 1; - segments = segments !== undefined ? Math.max( 3, segments ) : 8; - - thetaStart = thetaStart !== undefined ? thetaStart : 0; - thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; - - // buffers - - var indices = []; - var vertices = []; - var normals = []; - var uvs = []; - - // helper variables - - var i, s; - var vertex = new Vector3(); - var uv = new Vector2(); - - // center point - - vertices.push( 0, 0, 0 ); - normals.push( 0, 0, 1 ); - uvs.push( 0.5, 0.5 ); - - for ( s = 0, i = 3; s <= segments; s ++, i += 3 ) { - - var segment = thetaStart + s / segments * thetaLength; - - // vertex - - vertex.x = radius * Math.cos( segment ); - vertex.y = radius * Math.sin( segment ); - - vertices.push( vertex.x, vertex.y, vertex.z ); - - // normal - - normals.push( 0, 0, 1 ); - - // uvs - - uv.x = ( vertices[ i ] / radius + 1 ) / 2; - uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; - - uvs.push( uv.x, uv.y ); - - } - - // indices - - for ( i = 1; i <= segments; i ++ ) { - - indices.push( i, i + 1, 0 ); - - } - - // build geometry - - this.setIndex( indices ); - this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); - this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); - -} - -CircleBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); -CircleBufferGeometry.prototype.constructor = CircleBufferGeometry; - - - -var Geometries = Object.freeze({ - WireframeGeometry: WireframeGeometry, - ParametricGeometry: ParametricGeometry, - ParametricBufferGeometry: ParametricBufferGeometry, - TetrahedronGeometry: TetrahedronGeometry, - TetrahedronBufferGeometry: TetrahedronBufferGeometry, - OctahedronGeometry: OctahedronGeometry, - OctahedronBufferGeometry: OctahedronBufferGeometry, - IcosahedronGeometry: IcosahedronGeometry, - IcosahedronBufferGeometry: IcosahedronBufferGeometry, - DodecahedronGeometry: DodecahedronGeometry, - DodecahedronBufferGeometry: DodecahedronBufferGeometry, - PolyhedronGeometry: PolyhedronGeometry, - PolyhedronBufferGeometry: PolyhedronBufferGeometry, - TubeGeometry: TubeGeometry, - TubeBufferGeometry: TubeBufferGeometry, - TorusKnotGeometry: TorusKnotGeometry, - TorusKnotBufferGeometry: TorusKnotBufferGeometry, - TorusGeometry: TorusGeometry, - TorusBufferGeometry: TorusBufferGeometry, - TextGeometry: TextGeometry, - TextBufferGeometry: TextBufferGeometry, - SphereGeometry: SphereGeometry, - SphereBufferGeometry: SphereBufferGeometry, - RingGeometry: RingGeometry, - RingBufferGeometry: RingBufferGeometry, - PlaneGeometry: PlaneGeometry, - PlaneBufferGeometry: PlaneBufferGeometry, - LatheGeometry: LatheGeometry, - LatheBufferGeometry: LatheBufferGeometry, - ShapeGeometry: ShapeGeometry, - ShapeBufferGeometry: ShapeBufferGeometry, - ExtrudeGeometry: ExtrudeGeometry, - ExtrudeBufferGeometry: ExtrudeBufferGeometry, - EdgesGeometry: EdgesGeometry, - ConeGeometry: ConeGeometry, - ConeBufferGeometry: ConeBufferGeometry, - CylinderGeometry: CylinderGeometry, - CylinderBufferGeometry: CylinderBufferGeometry, - CircleGeometry: CircleGeometry, - CircleBufferGeometry: CircleBufferGeometry, - BoxGeometry: BoxGeometry, - BoxBufferGeometry: BoxBufferGeometry -}); - -/** - * @author mrdoob / http://mrdoob.com/ - * - * parameters = { - * color: , - * opacity: - * } - */ - -function ShadowMaterial( parameters ) { - - Material.call( this ); - - this.type = 'ShadowMaterial'; - - this.color = new Color( 0x000000 ); - this.opacity = 1.0; - - this.lights = true; - this.transparent = true; - - this.setValues( parameters ); - -} - -ShadowMaterial.prototype = Object.create( Material.prototype ); -ShadowMaterial.prototype.constructor = ShadowMaterial; - -ShadowMaterial.prototype.isShadowMaterial = true; - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function RawShaderMaterial( parameters ) { - - ShaderMaterial.call( this, parameters ); - - this.type = 'RawShaderMaterial'; - -} - -RawShaderMaterial.prototype = Object.create( ShaderMaterial.prototype ); -RawShaderMaterial.prototype.constructor = RawShaderMaterial; - -RawShaderMaterial.prototype.isRawShaderMaterial = true; - -/** - * @author WestLangley / http://github.com/WestLangley - * - * parameters = { - * color: , - * roughness: , - * metalness: , - * opacity: , - * - * map: new THREE.Texture( ), - * - * lightMap: new THREE.Texture( ), - * lightMapIntensity: - * - * aoMap: new THREE.Texture( ), - * aoMapIntensity: - * - * emissive: , - * emissiveIntensity: - * emissiveMap: new THREE.Texture( ), - * - * bumpMap: new THREE.Texture( ), - * bumpScale: , - * - * normalMap: new THREE.Texture( ), - * normalScale: , - * - * displacementMap: new THREE.Texture( ), - * displacementScale: , - * displacementBias: , - * - * roughnessMap: new THREE.Texture( ), - * - * metalnessMap: new THREE.Texture( ), - * - * alphaMap: new THREE.Texture( ), - * - * envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ), - * envMapIntensity: - * - * refractionRatio: , - * - * wireframe: , - * wireframeLinewidth: , - * - * skinning: , - * morphTargets: , - * morphNormals: - * } - */ - -function MeshStandardMaterial( parameters ) { - - Material.call( this ); - - this.defines = { 'STANDARD': '' }; - - this.type = 'MeshStandardMaterial'; - - this.color = new Color( 0xffffff ); // diffuse - this.roughness = 0.5; - this.metalness = 0.5; - - this.map = null; - - this.lightMap = null; - this.lightMapIntensity = 1.0; - - this.aoMap = null; - this.aoMapIntensity = 1.0; - - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; - - this.bumpMap = null; - this.bumpScale = 1; - - this.normalMap = null; - this.normalScale = new Vector2( 1, 1 ); - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.roughnessMap = null; - - this.metalnessMap = null; - - this.alphaMap = null; - - this.envMap = null; - this.envMapIntensity = 1.0; - - this.refractionRatio = 0.98; - - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.skinning = false; - this.morphTargets = false; - this.morphNormals = false; - - this.setValues( parameters ); - -} - -MeshStandardMaterial.prototype = Object.create( Material.prototype ); -MeshStandardMaterial.prototype.constructor = MeshStandardMaterial; - -MeshStandardMaterial.prototype.isMeshStandardMaterial = true; - -MeshStandardMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.defines = { 'STANDARD': '' }; - - this.color.copy( source.color ); - this.roughness = source.roughness; - this.metalness = source.metalness; - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.roughnessMap = source.roughnessMap; - - this.metalnessMap = source.metalnessMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.envMapIntensity = source.envMapIntensity; - - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.skinning = source.skinning; - this.morphTargets = source.morphTargets; - this.morphNormals = source.morphNormals; - - return this; - -}; - -/** - * @author WestLangley / http://github.com/WestLangley - * - * parameters = { - * reflectivity: - * } - */ - -function MeshPhysicalMaterial( parameters ) { - - MeshStandardMaterial.call( this ); - - this.defines = { 'PHYSICAL': '' }; - - this.type = 'MeshPhysicalMaterial'; - - this.reflectivity = 0.5; // maps to F0 = 0.04 - - this.clearCoat = 0.0; - this.clearCoatRoughness = 0.0; - - this.setValues( parameters ); - -} - -MeshPhysicalMaterial.prototype = Object.create( MeshStandardMaterial.prototype ); -MeshPhysicalMaterial.prototype.constructor = MeshPhysicalMaterial; - -MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true; - -MeshPhysicalMaterial.prototype.copy = function ( source ) { - - MeshStandardMaterial.prototype.copy.call( this, source ); - - this.defines = { 'PHYSICAL': '' }; - - this.reflectivity = source.reflectivity; - - this.clearCoat = source.clearCoat; - this.clearCoatRoughness = source.clearCoatRoughness; - - return this; - -}; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * - * parameters = { - * color: , - * specular: , - * shininess: , - * opacity: , - * - * map: new THREE.Texture( ), - * - * lightMap: new THREE.Texture( ), - * lightMapIntensity: - * - * aoMap: new THREE.Texture( ), - * aoMapIntensity: - * - * emissive: , - * emissiveIntensity: - * emissiveMap: new THREE.Texture( ), - * - * bumpMap: new THREE.Texture( ), - * bumpScale: , - * - * normalMap: new THREE.Texture( ), - * normalScale: , - * - * displacementMap: new THREE.Texture( ), - * displacementScale: , - * displacementBias: , - * - * specularMap: new THREE.Texture( ), - * - * alphaMap: new THREE.Texture( ), - * - * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), - * combine: THREE.Multiply, - * reflectivity: , - * refractionRatio: , - * - * wireframe: , - * wireframeLinewidth: , - * - * skinning: , - * morphTargets: , - * morphNormals: - * } - */ - -function MeshPhongMaterial( parameters ) { - - Material.call( this ); - - this.type = 'MeshPhongMaterial'; - - this.color = new Color( 0xffffff ); // diffuse - this.specular = new Color( 0x111111 ); - this.shininess = 30; - - this.map = null; - - this.lightMap = null; - this.lightMapIntensity = 1.0; - - this.aoMap = null; - this.aoMapIntensity = 1.0; - - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; - - this.bumpMap = null; - this.bumpScale = 1; - - this.normalMap = null; - this.normalScale = new Vector2( 1, 1 ); - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.specularMap = null; - - this.alphaMap = null; - - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; - - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.skinning = false; - this.morphTargets = false; - this.morphNormals = false; - - this.setValues( parameters ); - -} - -MeshPhongMaterial.prototype = Object.create( Material.prototype ); -MeshPhongMaterial.prototype.constructor = MeshPhongMaterial; - -MeshPhongMaterial.prototype.isMeshPhongMaterial = true; - -MeshPhongMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.color.copy( source.color ); - this.specular.copy( source.specular ); - this.shininess = source.shininess; - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.specularMap = source.specularMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.skinning = source.skinning; - this.morphTargets = source.morphTargets; - this.morphNormals = source.morphNormals; - - return this; - -}; - -/** - * @author takahirox / http://github.com/takahirox - * - * parameters = { - * gradientMap: new THREE.Texture( ) - * } - */ - -function MeshToonMaterial( parameters ) { - - MeshPhongMaterial.call( this ); - - this.defines = { 'TOON': '' }; - - this.type = 'MeshToonMaterial'; - - this.gradientMap = null; - - this.setValues( parameters ); - -} - -MeshToonMaterial.prototype = Object.create( MeshPhongMaterial.prototype ); -MeshToonMaterial.prototype.constructor = MeshToonMaterial; - -MeshToonMaterial.prototype.isMeshToonMaterial = true; - -MeshToonMaterial.prototype.copy = function ( source ) { - - MeshPhongMaterial.prototype.copy.call( this, source ); - - this.gradientMap = source.gradientMap; - - return this; - -}; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author WestLangley / http://github.com/WestLangley - * - * parameters = { - * opacity: , - * - * bumpMap: new THREE.Texture( ), - * bumpScale: , - * - * normalMap: new THREE.Texture( ), - * normalScale: , - * - * displacementMap: new THREE.Texture( ), - * displacementScale: , - * displacementBias: , - * - * wireframe: , - * wireframeLinewidth: - * - * skinning: , - * morphTargets: , - * morphNormals: - * } - */ - -function MeshNormalMaterial( parameters ) { - - Material.call( this ); - - this.type = 'MeshNormalMaterial'; - - this.bumpMap = null; - this.bumpScale = 1; - - this.normalMap = null; - this.normalScale = new Vector2( 1, 1 ); - - this.displacementMap = null; - this.displacementScale = 1; - this.displacementBias = 0; - - this.wireframe = false; - this.wireframeLinewidth = 1; - - this.fog = false; - this.lights = false; - - this.skinning = false; - this.morphTargets = false; - this.morphNormals = false; - - this.setValues( parameters ); - -} - -MeshNormalMaterial.prototype = Object.create( Material.prototype ); -MeshNormalMaterial.prototype.constructor = MeshNormalMaterial; - -MeshNormalMaterial.prototype.isMeshNormalMaterial = true; - -MeshNormalMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.bumpMap = source.bumpMap; - this.bumpScale = source.bumpScale; - - this.normalMap = source.normalMap; - this.normalScale.copy( source.normalScale ); - - this.displacementMap = source.displacementMap; - this.displacementScale = source.displacementScale; - this.displacementBias = source.displacementBias; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - - this.skinning = source.skinning; - this.morphTargets = source.morphTargets; - this.morphNormals = source.morphNormals; - - return this; - -}; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - * - * parameters = { - * color: , - * opacity: , - * - * map: new THREE.Texture( ), - * - * lightMap: new THREE.Texture( ), - * lightMapIntensity: - * - * aoMap: new THREE.Texture( ), - * aoMapIntensity: - * - * emissive: , - * emissiveIntensity: - * emissiveMap: new THREE.Texture( ), - * - * specularMap: new THREE.Texture( ), - * - * alphaMap: new THREE.Texture( ), - * - * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), - * combine: THREE.Multiply, - * reflectivity: , - * refractionRatio: , - * - * wireframe: , - * wireframeLinewidth: , - * - * skinning: , - * morphTargets: , - * morphNormals: - * } - */ - -function MeshLambertMaterial( parameters ) { - - Material.call( this ); - - this.type = 'MeshLambertMaterial'; - - this.color = new Color( 0xffffff ); // diffuse - - this.map = null; - - this.lightMap = null; - this.lightMapIntensity = 1.0; - - this.aoMap = null; - this.aoMapIntensity = 1.0; - - this.emissive = new Color( 0x000000 ); - this.emissiveIntensity = 1.0; - this.emissiveMap = null; - - this.specularMap = null; - - this.alphaMap = null; - - this.envMap = null; - this.combine = MultiplyOperation; - this.reflectivity = 1; - this.refractionRatio = 0.98; - - this.wireframe = false; - this.wireframeLinewidth = 1; - this.wireframeLinecap = 'round'; - this.wireframeLinejoin = 'round'; - - this.skinning = false; - this.morphTargets = false; - this.morphNormals = false; - - this.setValues( parameters ); - -} - -MeshLambertMaterial.prototype = Object.create( Material.prototype ); -MeshLambertMaterial.prototype.constructor = MeshLambertMaterial; - -MeshLambertMaterial.prototype.isMeshLambertMaterial = true; - -MeshLambertMaterial.prototype.copy = function ( source ) { - - Material.prototype.copy.call( this, source ); - - this.color.copy( source.color ); - - this.map = source.map; - - this.lightMap = source.lightMap; - this.lightMapIntensity = source.lightMapIntensity; - - this.aoMap = source.aoMap; - this.aoMapIntensity = source.aoMapIntensity; - - this.emissive.copy( source.emissive ); - this.emissiveMap = source.emissiveMap; - this.emissiveIntensity = source.emissiveIntensity; - - this.specularMap = source.specularMap; - - this.alphaMap = source.alphaMap; - - this.envMap = source.envMap; - this.combine = source.combine; - this.reflectivity = source.reflectivity; - this.refractionRatio = source.refractionRatio; - - this.wireframe = source.wireframe; - this.wireframeLinewidth = source.wireframeLinewidth; - this.wireframeLinecap = source.wireframeLinecap; - this.wireframeLinejoin = source.wireframeLinejoin; - - this.skinning = source.skinning; - this.morphTargets = source.morphTargets; - this.morphNormals = source.morphNormals; - - return this; - -}; - -/** - * @author alteredq / http://alteredqualia.com/ - * - * parameters = { - * color: , - * opacity: , - * - * linewidth: , - * - * scale: , - * dashSize: , - * gapSize: - * } - */ - -function LineDashedMaterial( parameters ) { - - LineBasicMaterial.call( this ); - - this.type = 'LineDashedMaterial'; - - this.scale = 1; - this.dashSize = 3; - this.gapSize = 1; - - this.setValues( parameters ); - -} - -LineDashedMaterial.prototype = Object.create( LineBasicMaterial.prototype ); -LineDashedMaterial.prototype.constructor = LineDashedMaterial; - -LineDashedMaterial.prototype.isLineDashedMaterial = true; - -LineDashedMaterial.prototype.copy = function ( source ) { - - LineBasicMaterial.prototype.copy.call( this, source ); - - this.scale = source.scale; - this.dashSize = source.dashSize; - this.gapSize = source.gapSize; - - return this; - -}; - - - -var Materials = Object.freeze({ - ShadowMaterial: ShadowMaterial, - SpriteMaterial: SpriteMaterial, - RawShaderMaterial: RawShaderMaterial, - ShaderMaterial: ShaderMaterial, - PointsMaterial: PointsMaterial, - MeshPhysicalMaterial: MeshPhysicalMaterial, - MeshStandardMaterial: MeshStandardMaterial, - MeshPhongMaterial: MeshPhongMaterial, - MeshToonMaterial: MeshToonMaterial, - MeshNormalMaterial: MeshNormalMaterial, - MeshLambertMaterial: MeshLambertMaterial, - MeshDepthMaterial: MeshDepthMaterial, - MeshDistanceMaterial: MeshDistanceMaterial, - MeshBasicMaterial: MeshBasicMaterial, - LineDashedMaterial: LineDashedMaterial, - LineBasicMaterial: LineBasicMaterial, - Material: Material -}); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -var Cache = { - - enabled: false, - - files: {}, - - add: function ( key, file ) { - - if ( this.enabled === false ) return; - - // console.log( 'THREE.Cache', 'Adding key:', key ); - - this.files[ key ] = file; - - }, - - get: function ( key ) { - - if ( this.enabled === false ) return; - - // console.log( 'THREE.Cache', 'Checking key:', key ); - - return this.files[ key ]; - - }, - - remove: function ( key ) { - - delete this.files[ key ]; - - }, - - clear: function () { - - this.files = {}; - - } - -}; - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function LoadingManager( onLoad, onProgress, onError ) { - - var scope = this; - - var isLoading = false; - var itemsLoaded = 0; - var itemsTotal = 0; - var urlModifier = undefined; - - this.onStart = undefined; - this.onLoad = onLoad; - this.onProgress = onProgress; - this.onError = onError; - - this.itemStart = function ( url ) { - - itemsTotal ++; - - if ( isLoading === false ) { - - if ( scope.onStart !== undefined ) { - - scope.onStart( url, itemsLoaded, itemsTotal ); - - } - - } - - isLoading = true; - - }; - - this.itemEnd = function ( url ) { - - itemsLoaded ++; - - if ( scope.onProgress !== undefined ) { - - scope.onProgress( url, itemsLoaded, itemsTotal ); - - } - - if ( itemsLoaded === itemsTotal ) { - - isLoading = false; - - if ( scope.onLoad !== undefined ) { - - scope.onLoad(); - - } - - } - - }; - - this.itemError = function ( url ) { - - if ( scope.onError !== undefined ) { - - scope.onError( url ); - - } - - }; - - this.resolveURL = function ( url ) { - - if ( urlModifier ) { - - return urlModifier( url ); - - } - - return url; - - }; - - this.setURLModifier = function ( transform ) { - - urlModifier = transform; - - }; - -} - -var DefaultLoadingManager = new LoadingManager(); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -var loading = {}; - -function FileLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - -} - -Object.assign( FileLoader.prototype, { - - load: function ( url, onLoad, onProgress, onError ) { - - if ( url === undefined ) url = ''; - - if ( this.path !== undefined ) url = this.path + url; - - url = this.manager.resolveURL( url ); - - var scope = this; - - var cached = Cache.get( url ); - - if ( cached !== undefined ) { - - scope.manager.itemStart( url ); - - setTimeout( function () { - - if ( onLoad ) onLoad( cached ); - - scope.manager.itemEnd( url ); - - }, 0 ); - - return cached; - - } - - // Check if request is duplicate - - if ( loading[ url ] !== undefined ) { - - loading[ url ].push( { - - onLoad: onLoad, - onProgress: onProgress, - onError: onError - - } ); - - return; - - } - - // Check for data: URI - var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/; - var dataUriRegexResult = url.match( dataUriRegex ); - - // Safari can not handle Data URIs through XMLHttpRequest so process manually - if ( dataUriRegexResult ) { - - var mimeType = dataUriRegexResult[ 1 ]; - var isBase64 = !! dataUriRegexResult[ 2 ]; - var data = dataUriRegexResult[ 3 ]; - - data = window.decodeURIComponent( data ); - - if ( isBase64 ) data = window.atob( data ); - - try { - - var response; - var responseType = ( this.responseType || '' ).toLowerCase(); - - switch ( responseType ) { - - case 'arraybuffer': - case 'blob': - - var view = new Uint8Array( data.length ); - - for ( var i = 0; i < data.length; i ++ ) { - - view[ i ] = data.charCodeAt( i ); - - } - - if ( responseType === 'blob' ) { - - response = new Blob( [ view.buffer ], { type: mimeType } ); - - } else { - - response = view.buffer; - - } - - break; - - case 'document': - - var parser = new DOMParser(); - response = parser.parseFromString( data, mimeType ); - - break; - - case 'json': - - response = JSON.parse( data ); - - break; - - default: // 'text' or other - - response = data; - - break; - - } - - // Wait for next browser tick like standard XMLHttpRequest event dispatching does - window.setTimeout( function () { - - if ( onLoad ) onLoad( response ); - - scope.manager.itemEnd( url ); - - }, 0 ); - - } catch ( error ) { - - // Wait for next browser tick like standard XMLHttpRequest event dispatching does - window.setTimeout( function () { - - if ( onError ) onError( error ); - - scope.manager.itemEnd( url ); - scope.manager.itemError( url ); - - }, 0 ); - - } - - } else { - - // Initialise array for duplicate requests - - loading[ url ] = []; - - loading[ url ].push( { - - onLoad: onLoad, - onProgress: onProgress, - onError: onError - - } ); - - var request = new XMLHttpRequest(); - - request.open( 'GET', url, true ); - - request.addEventListener( 'load', function ( event ) { - - var response = event.target.response; - - Cache.add( url, response ); - - var callbacks = loading[ url ]; - - delete loading[ url ]; - - if ( this.status === 200 ) { - - for ( var i = 0, il = callbacks.length; i < il; i ++ ) { - - var callback = callbacks[ i ]; - if ( callback.onLoad ) callback.onLoad( response ); - - } - - scope.manager.itemEnd( url ); - - } else if ( this.status === 0 ) { - - // Some browsers return HTTP Status 0 when using non-http protocol - // e.g. 'file://' or 'data://'. Handle as success. - - console.warn( 'THREE.FileLoader: HTTP Status 0 received.' ); - - for ( var i = 0, il = callbacks.length; i < il; i ++ ) { - - var callback = callbacks[ i ]; - if ( callback.onLoad ) callback.onLoad( response ); - - } - - scope.manager.itemEnd( url ); - - } else { - - for ( var i = 0, il = callbacks.length; i < il; i ++ ) { - - var callback = callbacks[ i ]; - if ( callback.onError ) callback.onError( event ); - - } - - scope.manager.itemEnd( url ); - scope.manager.itemError( url ); - - } - - }, false ); - - request.addEventListener( 'progress', function ( event ) { - - var callbacks = loading[ url ]; - - for ( var i = 0, il = callbacks.length; i < il; i ++ ) { - - var callback = callbacks[ i ]; - if ( callback.onProgress ) callback.onProgress( event ); - - } - - }, false ); - - request.addEventListener( 'error', function ( event ) { - - var callbacks = loading[ url ]; - - delete loading[ url ]; - - for ( var i = 0, il = callbacks.length; i < il; i ++ ) { - - var callback = callbacks[ i ]; - if ( callback.onError ) callback.onError( event ); - - } - - scope.manager.itemEnd( url ); - scope.manager.itemError( url ); - - }, false ); - - if ( this.responseType !== undefined ) request.responseType = this.responseType; - if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials; - - if ( request.overrideMimeType ) request.overrideMimeType( this.mimeType !== undefined ? this.mimeType : 'text/plain' ); - - for ( var header in this.requestHeader ) { - - request.setRequestHeader( header, this.requestHeader[ header ] ); - - } - - request.send( null ); - - } - - scope.manager.itemStart( url ); - - return request; - - }, - - setPath: function ( value ) { - - this.path = value; - return this; - - }, - - setResponseType: function ( value ) { - - this.responseType = value; - return this; - - }, - - setWithCredentials: function ( value ) { - - this.withCredentials = value; - return this; - - }, - - setMimeType: function ( value ) { - - this.mimeType = value; - return this; - - }, - - setRequestHeader: function ( value ) { - - this.requestHeader = value; - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * - * Abstract Base class to block based textures loader (dds, pvr, ...) - */ - -function CompressedTextureLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - - // override in sub classes - this._parser = null; - -} - -Object.assign( CompressedTextureLoader.prototype, { - - load: function ( url, onLoad, onProgress, onError ) { - - var scope = this; - - var images = []; - - var texture = new CompressedTexture(); - texture.image = images; - - var loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.setResponseType( 'arraybuffer' ); - - function loadTexture( i ) { - - loader.load( url[ i ], function ( buffer ) { - - var texDatas = scope._parser( buffer, true ); - - images[ i ] = { - width: texDatas.width, - height: texDatas.height, - format: texDatas.format, - mipmaps: texDatas.mipmaps - }; - - loaded += 1; - - if ( loaded === 6 ) { - - if ( texDatas.mipmapCount === 1 ) - texture.minFilter = LinearFilter; - - texture.format = texDatas.format; - texture.needsUpdate = true; - - if ( onLoad ) onLoad( texture ); - - } - - }, onProgress, onError ); - - } - - if ( Array.isArray( url ) ) { - - var loaded = 0; - - for ( var i = 0, il = url.length; i < il; ++ i ) { - - loadTexture( i ); - - } - - } else { - - // compressed cubemap texture stored in a single DDS file - - loader.load( url, function ( buffer ) { - - var texDatas = scope._parser( buffer, true ); - - if ( texDatas.isCubemap ) { - - var faces = texDatas.mipmaps.length / texDatas.mipmapCount; - - for ( var f = 0; f < faces; f ++ ) { - - images[ f ] = { mipmaps: [] }; - - for ( var i = 0; i < texDatas.mipmapCount; i ++ ) { - - images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] ); - images[ f ].format = texDatas.format; - images[ f ].width = texDatas.width; - images[ f ].height = texDatas.height; - - } - - } - - } else { - - texture.image.width = texDatas.width; - texture.image.height = texDatas.height; - texture.mipmaps = texDatas.mipmaps; - - } - - if ( texDatas.mipmapCount === 1 ) { - - texture.minFilter = LinearFilter; - - } - - texture.format = texDatas.format; - texture.needsUpdate = true; - - if ( onLoad ) onLoad( texture ); - - }, onProgress, onError ); - - } - - return texture; - - }, - - setPath: function ( value ) { - - this.path = value; - return this; - - } - -} ); - -/** - * @author Nikos M. / https://github.com/foo123/ - * - * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...) - */ - -function DataTextureLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - - // override in sub classes - this._parser = null; - -} - -Object.assign( DataTextureLoader.prototype, { - - load: function ( url, onLoad, onProgress, onError ) { - - var scope = this; - - var texture = new DataTexture(); - - var loader = new FileLoader( this.manager ); - loader.setResponseType( 'arraybuffer' ); - - loader.load( url, function ( buffer ) { - - var texData = scope._parser( buffer ); - - if ( ! texData ) return; - - if ( undefined !== texData.image ) { - - texture.image = texData.image; - - } else if ( undefined !== texData.data ) { - - texture.image.width = texData.width; - texture.image.height = texData.height; - texture.image.data = texData.data; - - } - - texture.wrapS = undefined !== texData.wrapS ? texData.wrapS : ClampToEdgeWrapping; - texture.wrapT = undefined !== texData.wrapT ? texData.wrapT : ClampToEdgeWrapping; - - texture.magFilter = undefined !== texData.magFilter ? texData.magFilter : LinearFilter; - texture.minFilter = undefined !== texData.minFilter ? texData.minFilter : LinearMipMapLinearFilter; - - texture.anisotropy = undefined !== texData.anisotropy ? texData.anisotropy : 1; - - if ( undefined !== texData.format ) { - - texture.format = texData.format; - - } - if ( undefined !== texData.type ) { - - texture.type = texData.type; - - } - - if ( undefined !== texData.mipmaps ) { - - texture.mipmaps = texData.mipmaps; - - } - - if ( 1 === texData.mipmapCount ) { - - texture.minFilter = LinearFilter; - - } - - texture.needsUpdate = true; - - if ( onLoad ) onLoad( texture, texData ); - - }, onProgress, onError ); - - - return texture; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function ImageLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - -} - -Object.assign( ImageLoader.prototype, { - - crossOrigin: 'Anonymous', - - load: function ( url, onLoad, onProgress, onError ) { - - if ( url === undefined ) url = ''; - - if ( this.path !== undefined ) url = this.path + url; - - url = this.manager.resolveURL( url ); - - var scope = this; - - var cached = Cache.get( url ); - - if ( cached !== undefined ) { - - scope.manager.itemStart( url ); - - setTimeout( function () { - - if ( onLoad ) onLoad( cached ); - - scope.manager.itemEnd( url ); - - }, 0 ); - - return cached; - - } - - var image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' ); - - image.addEventListener( 'load', function () { - - Cache.add( url, this ); - - if ( onLoad ) onLoad( this ); - - scope.manager.itemEnd( url ); - - }, false ); - - /* - image.addEventListener( 'progress', function ( event ) { - - if ( onProgress ) onProgress( event ); - - }, false ); - */ - - image.addEventListener( 'error', function ( event ) { - - if ( onError ) onError( event ); - - scope.manager.itemEnd( url ); - scope.manager.itemError( url ); - - }, false ); - - if ( url.substr( 0, 5 ) !== 'data:' ) { - - if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; - - } - - scope.manager.itemStart( url ); - - image.src = url; - - return image; - - }, - - setCrossOrigin: function ( value ) { - - this.crossOrigin = value; - return this; - - }, - - setPath: function ( value ) { - - this.path = value; - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function CubeTextureLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - -} - -Object.assign( CubeTextureLoader.prototype, { - - crossOrigin: 'Anonymous', - - load: function ( urls, onLoad, onProgress, onError ) { - - var texture = new CubeTexture(); - - var loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); - - var loaded = 0; - - function loadTexture( i ) { - - loader.load( urls[ i ], function ( image ) { - - texture.images[ i ] = image; - - loaded ++; - - if ( loaded === 6 ) { - - texture.needsUpdate = true; - - if ( onLoad ) onLoad( texture ); - - } - - }, undefined, onError ); - - } - - for ( var i = 0; i < urls.length; ++ i ) { - - loadTexture( i ); - - } - - return texture; - - }, - - setCrossOrigin: function ( value ) { - - this.crossOrigin = value; - return this; - - }, - - setPath: function ( value ) { - - this.path = value; - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function TextureLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - -} - -Object.assign( TextureLoader.prototype, { - - crossOrigin: 'Anonymous', - - load: function ( url, onLoad, onProgress, onError ) { - - var loader = new ImageLoader( this.manager ); - loader.setCrossOrigin( this.crossOrigin ); - loader.setPath( this.path ); - - var texture = new Texture(); - texture.image = loader.load( url, function () { - - // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB. - var isJPEG = url.search( /\.(jpg|jpeg)$/ ) > 0 || url.search( /^data\:image\/jpeg/ ) === 0; - - texture.format = isJPEG ? RGBFormat : RGBAFormat; - texture.needsUpdate = true; - - if ( onLoad !== undefined ) { - - onLoad( texture ); - - } - - }, onProgress, onError ); - - return texture; - - }, - - setCrossOrigin: function ( value ) { - - this.crossOrigin = value; - return this; - - }, - - setPath: function ( value ) { - - this.path = value; - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ - -function Light( color, intensity ) { - - Object3D.call( this ); - - this.type = 'Light'; - - this.color = new Color( color ); - this.intensity = intensity !== undefined ? intensity : 1; - - this.receiveShadow = undefined; - -} - -Light.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: Light, - - isLight: true, - - copy: function ( source ) { - - Object3D.prototype.copy.call( this, source ); - - this.color.copy( source.color ); - this.intensity = source.intensity; - - return this; - - }, - - toJSON: function ( meta ) { - - var data = Object3D.prototype.toJSON.call( this, meta ); - - data.object.color = this.color.getHex(); - data.object.intensity = this.intensity; - - if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex(); - - if ( this.distance !== undefined ) data.object.distance = this.distance; - if ( this.angle !== undefined ) data.object.angle = this.angle; - if ( this.decay !== undefined ) data.object.decay = this.decay; - if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; - - if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); - - return data; - - } - -} ); - -/** - * @author alteredq / http://alteredqualia.com/ - */ - -function HemisphereLight( skyColor, groundColor, intensity ) { - - Light.call( this, skyColor, intensity ); - - this.type = 'HemisphereLight'; - - this.castShadow = undefined; - - this.position.copy( Object3D.DefaultUp ); - this.updateMatrix(); - - this.groundColor = new Color( groundColor ); - -} - -HemisphereLight.prototype = Object.assign( Object.create( Light.prototype ), { - - constructor: HemisphereLight, - - isHemisphereLight: true, - - copy: function ( source ) { - - Light.prototype.copy.call( this, source ); - - this.groundColor.copy( source.groundColor ); - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function LightShadow( camera ) { - - this.camera = camera; - - this.bias = 0; - this.radius = 1; - - this.mapSize = new Vector2( 512, 512 ); - - this.map = null; - this.matrix = new Matrix4(); - -} - -Object.assign( LightShadow.prototype, { - - copy: function ( source ) { - - this.camera = source.camera.clone(); - - this.bias = source.bias; - this.radius = source.radius; - - this.mapSize.copy( source.mapSize ); - - return this; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - toJSON: function () { - - var object = {}; - - if ( this.bias !== 0 ) object.bias = this.bias; - if ( this.radius !== 1 ) object.radius = this.radius; - if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray(); - - object.camera = this.camera.toJSON( false ).object; - delete object.camera.matrix; - - return object; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function SpotLightShadow() { - - LightShadow.call( this, new PerspectiveCamera( 50, 1, 0.5, 500 ) ); - -} - -SpotLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), { - - constructor: SpotLightShadow, - - isSpotLightShadow: true, - - update: function ( light ) { - - var camera = this.camera; - - var fov = _Math.RAD2DEG * 2 * light.angle; - var aspect = this.mapSize.width / this.mapSize.height; - var far = light.distance || camera.far; - - if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) { - - camera.fov = fov; - camera.aspect = aspect; - camera.far = far; - camera.updateProjectionMatrix(); - - } - - } - -} ); - -/** - * @author alteredq / http://alteredqualia.com/ - */ - -function SpotLight( color, intensity, distance, angle, penumbra, decay ) { - - Light.call( this, color, intensity ); - - this.type = 'SpotLight'; - - this.position.copy( Object3D.DefaultUp ); - this.updateMatrix(); - - this.target = new Object3D(); - - Object.defineProperty( this, 'power', { - get: function () { - - // intensity = power per solid angle. - // ref: equation (17) from http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf - return this.intensity * Math.PI; - - }, - set: function ( power ) { - - // intensity = power per solid angle. - // ref: equation (17) from http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf - this.intensity = power / Math.PI; - - } - } ); - - this.distance = ( distance !== undefined ) ? distance : 0; - this.angle = ( angle !== undefined ) ? angle : Math.PI / 3; - this.penumbra = ( penumbra !== undefined ) ? penumbra : 0; - this.decay = ( decay !== undefined ) ? decay : 1; // for physically correct lights, should be 2. - - this.shadow = new SpotLightShadow(); - -} - -SpotLight.prototype = Object.assign( Object.create( Light.prototype ), { - - constructor: SpotLight, - - isSpotLight: true, - - copy: function ( source ) { - - Light.prototype.copy.call( this, source ); - - this.distance = source.distance; - this.angle = source.angle; - this.penumbra = source.penumbra; - this.decay = source.decay; - - this.target = source.target.clone(); - - this.shadow = source.shadow.clone(); - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - - -function PointLight( color, intensity, distance, decay ) { - - Light.call( this, color, intensity ); - - this.type = 'PointLight'; - - Object.defineProperty( this, 'power', { - get: function () { - - // intensity = power per solid angle. - // ref: equation (15) from http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf - return this.intensity * 4 * Math.PI; - - }, - set: function ( power ) { - - // intensity = power per solid angle. - // ref: equation (15) from http://www.frostbite.com/wp-content/uploads/2014/11/course_notes_moving_frostbite_to_pbr.pdf - this.intensity = power / ( 4 * Math.PI ); - - } - } ); - - this.distance = ( distance !== undefined ) ? distance : 0; - this.decay = ( decay !== undefined ) ? decay : 1; // for physically correct lights, should be 2. - - this.shadow = new LightShadow( new PerspectiveCamera( 90, 1, 0.5, 500 ) ); - -} - -PointLight.prototype = Object.assign( Object.create( Light.prototype ), { - - constructor: PointLight, - - isPointLight: true, - - copy: function ( source ) { - - Light.prototype.copy.call( this, source ); - - this.distance = source.distance; - this.decay = source.decay; - - this.shadow = source.shadow.clone(); - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function DirectionalLightShadow( ) { - - LightShadow.call( this, new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) ); - -} - -DirectionalLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), { - - constructor: DirectionalLightShadow - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ - -function DirectionalLight( color, intensity ) { - - Light.call( this, color, intensity ); - - this.type = 'DirectionalLight'; - - this.position.copy( Object3D.DefaultUp ); - this.updateMatrix(); - - this.target = new Object3D(); - - this.shadow = new DirectionalLightShadow(); - -} - -DirectionalLight.prototype = Object.assign( Object.create( Light.prototype ), { - - constructor: DirectionalLight, - - isDirectionalLight: true, - - copy: function ( source ) { - - Light.prototype.copy.call( this, source ); - - this.target = source.target.clone(); - - this.shadow = source.shadow.clone(); - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function AmbientLight( color, intensity ) { - - Light.call( this, color, intensity ); - - this.type = 'AmbientLight'; - - this.castShadow = undefined; - -} - -AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), { - - constructor: AmbientLight, - - isAmbientLight: true - -} ); - -/** - * @author abelnation / http://github.com/abelnation - */ - -function RectAreaLight( color, intensity, width, height ) { - - Light.call( this, color, intensity ); - - this.type = 'RectAreaLight'; - - this.position.set( 0, 1, 0 ); - this.updateMatrix(); - - this.width = ( width !== undefined ) ? width : 10; - this.height = ( height !== undefined ) ? height : 10; - - // TODO (abelnation): distance/decay - - // TODO (abelnation): update method for RectAreaLight to update transform to lookat target - - // TODO (abelnation): shadows - -} - -// TODO (abelnation): RectAreaLight update when light shape is changed -RectAreaLight.prototype = Object.assign( Object.create( Light.prototype ), { - - constructor: RectAreaLight, - - isRectAreaLight: true, - - copy: function ( source ) { - - Light.prototype.copy.call( this, source ); - - this.width = source.width; - this.height = source.height; - - return this; - - }, - - toJSON: function ( meta ) { - - var data = Light.prototype.toJSON.call( this, meta ); - - data.object.width = this.width; - data.object.height = this.height; - - return data; - - } - -} ); - -/** - * @author tschw - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - */ - -var AnimationUtils = { - - // same as Array.prototype.slice, but also works on typed arrays - arraySlice: function ( array, from, to ) { - - if ( AnimationUtils.isTypedArray( array ) ) { - - // in ios9 array.subarray(from, undefined) will return empty array - // but array.subarray(from) or array.subarray(from, len) is correct - return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) ); - - } - - return array.slice( from, to ); - - }, - - // converts an array to a specific type - convertArray: function ( array, type, forceClone ) { - - if ( ! array || // let 'undefined' and 'null' pass - ! forceClone && array.constructor === type ) return array; - - if ( typeof type.BYTES_PER_ELEMENT === 'number' ) { - - return new type( array ); // create typed array - - } - - return Array.prototype.slice.call( array ); // create Array - - }, - - isTypedArray: function ( object ) { - - return ArrayBuffer.isView( object ) && - ! ( object instanceof DataView ); - - }, - - // returns an array by which times and values can be sorted - getKeyframeOrder: function ( times ) { - - function compareTime( i, j ) { - - return times[ i ] - times[ j ]; - - } - - var n = times.length; - var result = new Array( n ); - for ( var i = 0; i !== n; ++ i ) result[ i ] = i; - - result.sort( compareTime ); - - return result; - - }, - - // uses the array previously returned by 'getKeyframeOrder' to sort data - sortedArray: function ( values, stride, order ) { - - var nValues = values.length; - var result = new values.constructor( nValues ); - - for ( var i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) { - - var srcOffset = order[ i ] * stride; - - for ( var j = 0; j !== stride; ++ j ) { - - result[ dstOffset ++ ] = values[ srcOffset + j ]; - - } - - } - - return result; - - }, - - // function for parsing AOS keyframe formats - flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) { - - var i = 1, key = jsonKeys[ 0 ]; - - while ( key !== undefined && key[ valuePropertyName ] === undefined ) { - - key = jsonKeys[ i ++ ]; - - } - - if ( key === undefined ) return; // no data - - var value = key[ valuePropertyName ]; - if ( value === undefined ) return; // no data - - if ( Array.isArray( value ) ) { - - do { - - value = key[ valuePropertyName ]; - - if ( value !== undefined ) { - - times.push( key.time ); - values.push.apply( values, value ); // push all elements - - } - - key = jsonKeys[ i ++ ]; - - } while ( key !== undefined ); - - } else if ( value.toArray !== undefined ) { - - // ...assume THREE.Math-ish - - do { - - value = key[ valuePropertyName ]; - - if ( value !== undefined ) { - - times.push( key.time ); - value.toArray( values, values.length ); - - } - - key = jsonKeys[ i ++ ]; - - } while ( key !== undefined ); - - } else { - - // otherwise push as-is - - do { - - value = key[ valuePropertyName ]; - - if ( value !== undefined ) { - - times.push( key.time ); - values.push( value ); - - } - - key = jsonKeys[ i ++ ]; - - } while ( key !== undefined ); - - } - - } - -}; - -/** - * Abstract base class of interpolants over parametric samples. - * - * The parameter domain is one dimensional, typically the time or a path - * along a curve defined by the data. - * - * The sample values can have any dimensionality and derived classes may - * apply special interpretations to the data. - * - * This class provides the interval seek in a Template Method, deferring - * the actual interpolation to derived classes. - * - * Time complexity is O(1) for linear access crossing at most two points - * and O(log N) for random access, where N is the number of positions. - * - * References: - * - * http://www.oodesign.com/template-method-pattern.html - * - * @author tschw - */ - -function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - this.parameterPositions = parameterPositions; - this._cachedIndex = 0; - - this.resultBuffer = resultBuffer !== undefined ? - resultBuffer : new sampleValues.constructor( sampleSize ); - this.sampleValues = sampleValues; - this.valueSize = sampleSize; - -} - -Object.assign( Interpolant.prototype, { - - evaluate: function ( t ) { - - var pp = this.parameterPositions, - i1 = this._cachedIndex, - - t1 = pp[ i1 ], - t0 = pp[ i1 - 1 ]; - - validate_interval: { - - seek: { - - var right; - - linear_scan: { - - //- See http://jsperf.com/comparison-to-undefined/3 - //- slower code: - //- - //- if ( t >= t1 || t1 === undefined ) { - forward_scan: if ( ! ( t < t1 ) ) { - - for ( var giveUpAt = i1 + 2; ; ) { - - if ( t1 === undefined ) { - - if ( t < t0 ) break forward_scan; - - // after end - - i1 = pp.length; - this._cachedIndex = i1; - return this.afterEnd_( i1 - 1, t, t0 ); - - } - - if ( i1 === giveUpAt ) break; // this loop - - t0 = t1; - t1 = pp[ ++ i1 ]; - - if ( t < t1 ) { - - // we have arrived at the sought interval - break seek; - - } - - } - - // prepare binary search on the right side of the index - right = pp.length; - break linear_scan; - - } - - //- slower code: - //- if ( t < t0 || t0 === undefined ) { - if ( ! ( t >= t0 ) ) { - - // looping? - - var t1global = pp[ 1 ]; - - if ( t < t1global ) { - - i1 = 2; // + 1, using the scan for the details - t0 = t1global; - - } - - // linear reverse scan - - for ( var giveUpAt = i1 - 2; ; ) { - - if ( t0 === undefined ) { - - // before start - - this._cachedIndex = 0; - return this.beforeStart_( 0, t, t1 ); - - } - - if ( i1 === giveUpAt ) break; // this loop - - t1 = t0; - t0 = pp[ -- i1 - 1 ]; - - if ( t >= t0 ) { - - // we have arrived at the sought interval - break seek; - - } - - } - - // prepare binary search on the left side of the index - right = i1; - i1 = 0; - break linear_scan; - - } - - // the interval is valid - - break validate_interval; - - } // linear scan - - // binary search - - while ( i1 < right ) { - - var mid = ( i1 + right ) >>> 1; - - if ( t < pp[ mid ] ) { - - right = mid; - - } else { - - i1 = mid + 1; - - } - - } - - t1 = pp[ i1 ]; - t0 = pp[ i1 - 1 ]; - - // check boundary cases, again - - if ( t0 === undefined ) { - - this._cachedIndex = 0; - return this.beforeStart_( 0, t, t1 ); - - } - - if ( t1 === undefined ) { - - i1 = pp.length; - this._cachedIndex = i1; - return this.afterEnd_( i1 - 1, t0, t ); - - } - - } // seek - - this._cachedIndex = i1; - - this.intervalChanged_( i1, t0, t1 ); - - } // validate_interval - - return this.interpolate_( i1, t0, t, t1 ); - - }, - - settings: null, // optional, subclass-specific settings structure - // Note: The indirection allows central control of many interpolants. - - // --- Protected interface - - DefaultSettings_: {}, - - getSettings_: function () { - - return this.settings || this.DefaultSettings_; - - }, - - copySampleValue_: function ( index ) { - - // copies a sample value to the result buffer - - var result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - offset = index * stride; - - for ( var i = 0; i !== stride; ++ i ) { - - result[ i ] = values[ offset + i ]; - - } - - return result; - - }, - - // Template methods for derived classes: - - interpolate_: function ( /* i1, t0, t, t1 */ ) { - - throw new Error( 'call to abstract method' ); - // implementations shall return this.resultBuffer - - }, - - intervalChanged_: function ( /* i1, t0, t1 */ ) { - - // empty - - } - -} ); - -//!\ DECLARE ALIAS AFTER assign prototype ! -Object.assign( Interpolant.prototype, { - - //( 0, t, t0 ), returns this.resultBuffer - beforeStart_: Interpolant.prototype.copySampleValue_, - - //( N-1, tN-1, t ), returns this.resultBuffer - afterEnd_: Interpolant.prototype.copySampleValue_, - -} ); - -/** - * Fast and simple cubic spline interpolant. - * - * It was derived from a Hermitian construction setting the first derivative - * at each sample position to the linear slope between neighboring positions - * over their parameter interval. - * - * @author tschw - */ - -function CubicInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); - - this._weightPrev = - 0; - this._offsetPrev = - 0; - this._weightNext = - 0; - this._offsetNext = - 0; - -} - -CubicInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), { - - constructor: CubicInterpolant, - - DefaultSettings_: { - - endingStart: ZeroCurvatureEnding, - endingEnd: ZeroCurvatureEnding - - }, - - intervalChanged_: function ( i1, t0, t1 ) { - - var pp = this.parameterPositions, - iPrev = i1 - 2, - iNext = i1 + 1, - - tPrev = pp[ iPrev ], - tNext = pp[ iNext ]; - - if ( tPrev === undefined ) { - - switch ( this.getSettings_().endingStart ) { - - case ZeroSlopeEnding: - - // f'(t0) = 0 - iPrev = i1; - tPrev = 2 * t0 - t1; - - break; - - case WrapAroundEnding: - - // use the other end of the curve - iPrev = pp.length - 2; - tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ]; - - break; - - default: // ZeroCurvatureEnding - - // f''(t0) = 0 a.k.a. Natural Spline - iPrev = i1; - tPrev = t1; - - } - - } - - if ( tNext === undefined ) { - - switch ( this.getSettings_().endingEnd ) { - - case ZeroSlopeEnding: - - // f'(tN) = 0 - iNext = i1; - tNext = 2 * t1 - t0; - - break; - - case WrapAroundEnding: - - // use the other end of the curve - iNext = 1; - tNext = t1 + pp[ 1 ] - pp[ 0 ]; - - break; - - default: // ZeroCurvatureEnding - - // f''(tN) = 0, a.k.a. Natural Spline - iNext = i1 - 1; - tNext = t0; - - } - - } - - var halfDt = ( t1 - t0 ) * 0.5, - stride = this.valueSize; - - this._weightPrev = halfDt / ( t0 - tPrev ); - this._weightNext = halfDt / ( tNext - t1 ); - this._offsetPrev = iPrev * stride; - this._offsetNext = iNext * stride; - - }, - - interpolate_: function ( i1, t0, t, t1 ) { - - var result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - - o1 = i1 * stride, o0 = o1 - stride, - oP = this._offsetPrev, oN = this._offsetNext, - wP = this._weightPrev, wN = this._weightNext, - - p = ( t - t0 ) / ( t1 - t0 ), - pp = p * p, - ppp = pp * p; - - // evaluate polynomials - - var sP = - wP * ppp + 2 * wP * pp - wP * p; - var s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1; - var s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p; - var sN = wN * ppp - wN * pp; - - // combine data linearly - - for ( var i = 0; i !== stride; ++ i ) { - - result[ i ] = - sP * values[ oP + i ] + - s0 * values[ o0 + i ] + - s1 * values[ o1 + i ] + - sN * values[ oN + i ]; - - } - - return result; - - } - -} ); - -/** - * @author tschw - */ - -function LinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); - -} - -LinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), { - - constructor: LinearInterpolant, - - interpolate_: function ( i1, t0, t, t1 ) { - - var result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - - offset1 = i1 * stride, - offset0 = offset1 - stride, - - weight1 = ( t - t0 ) / ( t1 - t0 ), - weight0 = 1 - weight1; - - for ( var i = 0; i !== stride; ++ i ) { - - result[ i ] = - values[ offset0 + i ] * weight0 + - values[ offset1 + i ] * weight1; - - } - - return result; - - } - -} ); - -/** - * - * Interpolant that evaluates to the sample value at the position preceeding - * the parameter. - * - * @author tschw - */ - -function DiscreteInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); - -} - -DiscreteInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), { - - constructor: DiscreteInterpolant, - - interpolate_: function ( i1 /*, t0, t, t1 */ ) { - - return this.copySampleValue_( i1 - 1 ); - - } - -} ); - -var KeyframeTrackPrototype; - -KeyframeTrackPrototype = { - - TimeBufferType: Float32Array, - ValueBufferType: Float32Array, - - DefaultInterpolation: InterpolateLinear, - - InterpolantFactoryMethodDiscrete: function ( result ) { - - return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result ); - - }, - - InterpolantFactoryMethodLinear: function ( result ) { - - return new LinearInterpolant( this.times, this.values, this.getValueSize(), result ); - - }, - - InterpolantFactoryMethodSmooth: function ( result ) { - - return new CubicInterpolant( this.times, this.values, this.getValueSize(), result ); - - }, - - setInterpolation: function ( interpolation ) { - - var factoryMethod; - - switch ( interpolation ) { - - case InterpolateDiscrete: - - factoryMethod = this.InterpolantFactoryMethodDiscrete; - - break; - - case InterpolateLinear: - - factoryMethod = this.InterpolantFactoryMethodLinear; - - break; - - case InterpolateSmooth: - - factoryMethod = this.InterpolantFactoryMethodSmooth; - - break; - - } - - if ( factoryMethod === undefined ) { - - var message = "unsupported interpolation for " + - this.ValueTypeName + " keyframe track named " + this.name; - - if ( this.createInterpolant === undefined ) { - - // fall back to default, unless the default itself is messed up - if ( interpolation !== this.DefaultInterpolation ) { - - this.setInterpolation( this.DefaultInterpolation ); - - } else { - - throw new Error( message ); // fatal, in this case - - } - - } - - console.warn( 'THREE.KeyframeTrackPrototype:', message ); - return; - - } - - this.createInterpolant = factoryMethod; - - }, - - getInterpolation: function () { - - switch ( this.createInterpolant ) { - - case this.InterpolantFactoryMethodDiscrete: - - return InterpolateDiscrete; - - case this.InterpolantFactoryMethodLinear: - - return InterpolateLinear; - - case this.InterpolantFactoryMethodSmooth: - - return InterpolateSmooth; - - } - - }, - - getValueSize: function () { - - return this.values.length / this.times.length; - - }, - - // move all keyframes either forwards or backwards in time - shift: function ( timeOffset ) { - - if ( timeOffset !== 0.0 ) { - - var times = this.times; - - for ( var i = 0, n = times.length; i !== n; ++ i ) { - - times[ i ] += timeOffset; - - } - - } - - return this; - - }, - - // scale all keyframe times by a factor (useful for frame <-> seconds conversions) - scale: function ( timeScale ) { - - if ( timeScale !== 1.0 ) { - - var times = this.times; - - for ( var i = 0, n = times.length; i !== n; ++ i ) { - - times[ i ] *= timeScale; - - } - - } - - return this; - - }, - - // removes keyframes before and after animation without changing any values within the range [startTime, endTime]. - // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values - trim: function ( startTime, endTime ) { - - var times = this.times, - nKeys = times.length, - from = 0, - to = nKeys - 1; - - while ( from !== nKeys && times[ from ] < startTime ) ++ from; - while ( to !== - 1 && times[ to ] > endTime ) -- to; - - ++ to; // inclusive -> exclusive bound - - if ( from !== 0 || to !== nKeys ) { - - // empty tracks are forbidden, so keep at least one keyframe - if ( from >= to ) to = Math.max( to, 1 ), from = to - 1; - - var stride = this.getValueSize(); - this.times = AnimationUtils.arraySlice( times, from, to ); - this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride ); - - } - - return this; - - }, - - // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable - validate: function () { - - var valid = true; - - var valueSize = this.getValueSize(); - if ( valueSize - Math.floor( valueSize ) !== 0 ) { - - console.error( 'THREE.KeyframeTrackPrototype: Invalid value size in track.', this ); - valid = false; - - } - - var times = this.times, - values = this.values, - - nKeys = times.length; - - if ( nKeys === 0 ) { - - console.error( 'THREE.KeyframeTrackPrototype: Track is empty.', this ); - valid = false; - - } - - var prevTime = null; - - for ( var i = 0; i !== nKeys; i ++ ) { - - var currTime = times[ i ]; - - if ( typeof currTime === 'number' && isNaN( currTime ) ) { - - console.error( 'THREE.KeyframeTrackPrototype: Time is not a valid number.', this, i, currTime ); - valid = false; - break; - - } - - if ( prevTime !== null && prevTime > currTime ) { - - console.error( 'THREE.KeyframeTrackPrototype: Out of order keys.', this, i, currTime, prevTime ); - valid = false; - break; - - } - - prevTime = currTime; - - } - - if ( values !== undefined ) { - - if ( AnimationUtils.isTypedArray( values ) ) { - - for ( var i = 0, n = values.length; i !== n; ++ i ) { - - var value = values[ i ]; - - if ( isNaN( value ) ) { - - console.error( 'THREE.KeyframeTrackPrototype: Value is not a valid number.', this, i, value ); - valid = false; - break; - - } - - } - - } - - } - - return valid; - - }, - - // removes equivalent sequential keys as common in morph target sequences - // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) - optimize: function () { - - var times = this.times, - values = this.values, - stride = this.getValueSize(), - - smoothInterpolation = this.getInterpolation() === InterpolateSmooth, - - writeIndex = 1, - lastIndex = times.length - 1; - - for ( var i = 1; i < lastIndex; ++ i ) { - - var keep = false; - - var time = times[ i ]; - var timeNext = times[ i + 1 ]; - - // remove adjacent keyframes scheduled at the same time - - if ( time !== timeNext && ( i !== 1 || time !== time[ 0 ] ) ) { - - if ( ! smoothInterpolation ) { - - // remove unnecessary keyframes same as their neighbors - - var offset = i * stride, - offsetP = offset - stride, - offsetN = offset + stride; - - for ( var j = 0; j !== stride; ++ j ) { - - var value = values[ offset + j ]; - - if ( value !== values[ offsetP + j ] || - value !== values[ offsetN + j ] ) { - - keep = true; - break; - - } - - } - - } else keep = true; - - } - - // in-place compaction - - if ( keep ) { - - if ( i !== writeIndex ) { - - times[ writeIndex ] = times[ i ]; - - var readOffset = i * stride, - writeOffset = writeIndex * stride; - - for ( var j = 0; j !== stride; ++ j ) - - values[ writeOffset + j ] = values[ readOffset + j ]; - - } - - ++ writeIndex; - - } - - } - - // flush last keyframe (compaction looks ahead) - - if ( lastIndex > 0 ) { - - times[ writeIndex ] = times[ lastIndex ]; - - for ( var readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) - - values[ writeOffset + j ] = values[ readOffset + j ]; - - ++ writeIndex; - - } - - if ( writeIndex !== times.length ) { - - this.times = AnimationUtils.arraySlice( times, 0, writeIndex ); - this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride ); - - } - - return this; - - } - -}; - -function KeyframeTrackConstructor( name, times, values, interpolation ) { - - if ( name === undefined ) throw new Error( 'track name is undefined' ); - - if ( times === undefined || times.length === 0 ) { - - throw new Error( 'no keyframes in track named ' + name ); - - } - - this.name = name; - - this.times = AnimationUtils.convertArray( times, this.TimeBufferType ); - this.values = AnimationUtils.convertArray( values, this.ValueBufferType ); - - this.setInterpolation( interpolation || this.DefaultInterpolation ); - - this.validate(); - this.optimize(); - -} - -/** - * - * A Track of vectored keyframe values. - * - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - */ - -function VectorKeyframeTrack( name, times, values, interpolation ) { - - KeyframeTrackConstructor.call( this, name, times, values, interpolation ); - -} - -VectorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrackPrototype ), { - - constructor: VectorKeyframeTrack, - - ValueTypeName: 'vector' - - // ValueBufferType is inherited - - // DefaultInterpolation is inherited - -} ); - -/** - * Spherical linear unit quaternion interpolant. - * - * @author tschw - */ - -function QuaternionLinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { - - Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); - -} - -QuaternionLinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), { - - constructor: QuaternionLinearInterpolant, - - interpolate_: function ( i1, t0, t, t1 ) { - - var result = this.resultBuffer, - values = this.sampleValues, - stride = this.valueSize, - - offset = i1 * stride, - - alpha = ( t - t0 ) / ( t1 - t0 ); - - for ( var end = offset + stride; offset !== end; offset += 4 ) { - - Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha ); - - } - - return result; - - } - -} ); - -/** - * - * A Track of quaternion keyframe values. - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - */ - -function QuaternionKeyframeTrack( name, times, values, interpolation ) { - - KeyframeTrackConstructor.call( this, name, times, values, interpolation ); - -} - -QuaternionKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrackPrototype ), { - - constructor: QuaternionKeyframeTrack, - - ValueTypeName: 'quaternion', - - // ValueBufferType is inherited - - DefaultInterpolation: InterpolateLinear, - - InterpolantFactoryMethodLinear: function ( result ) { - - return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result ); - - }, - - InterpolantFactoryMethodSmooth: undefined // not yet implemented - -} ); - -/** - * - * A Track of numeric keyframe values. - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - */ - -function NumberKeyframeTrack( name, times, values, interpolation ) { - - KeyframeTrackConstructor.call( this, name, times, values, interpolation ); - -} - -NumberKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrackPrototype ), { - - constructor: NumberKeyframeTrack, - - ValueTypeName: 'number' - - // ValueBufferType is inherited - - // DefaultInterpolation is inherited - -} ); - -/** - * - * A Track that interpolates Strings - * - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - */ - -function StringKeyframeTrack( name, times, values, interpolation ) { - - KeyframeTrackConstructor.call( this, name, times, values, interpolation ); - -} - -StringKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrackPrototype ), { - - constructor: StringKeyframeTrack, - - ValueTypeName: 'string', - ValueBufferType: Array, - - DefaultInterpolation: InterpolateDiscrete, - - InterpolantFactoryMethodLinear: undefined, - - InterpolantFactoryMethodSmooth: undefined - -} ); - -/** - * - * A Track of Boolean keyframe values. - * - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - */ - -function BooleanKeyframeTrack( name, times, values ) { - - KeyframeTrackConstructor.call( this, name, times, values ); - -} - -BooleanKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrackPrototype ), { - - constructor: BooleanKeyframeTrack, - - ValueTypeName: 'bool', - ValueBufferType: Array, - - DefaultInterpolation: InterpolateDiscrete, - - InterpolantFactoryMethodLinear: undefined, - InterpolantFactoryMethodSmooth: undefined - - // Note: Actually this track could have a optimized / compressed - // representation of a single value and a custom interpolant that - // computes "firstValue ^ isOdd( index )". - -} ); - -/** - * - * A Track of keyframe values that represent color. - * - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - */ - -function ColorKeyframeTrack( name, times, values, interpolation ) { - - KeyframeTrackConstructor.call( this, name, times, values, interpolation ); - -} - -ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrackPrototype ), { - - constructor: ColorKeyframeTrack, - - ValueTypeName: 'color' - - // ValueBufferType is inherited - - // DefaultInterpolation is inherited - - - // Note: Very basic implementation and nothing special yet. - // However, this is the place for color space parameterization. - -} ); - -/** - * - * A timed sequence of keyframes for a specific property. - * - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - */ - -function KeyframeTrack( name, times, values, interpolation ) { - - KeyframeTrackConstructor.apply( this, name, times, values, interpolation ); - -} - -KeyframeTrack.prototype = KeyframeTrackPrototype; -KeyframeTrackPrototype.constructor = KeyframeTrack; - -// Static methods: - -Object.assign( KeyframeTrack, { - - // Serialization (in static context, because of constructor invocation - // and automatic invocation of .toJSON): - - parse: function ( json ) { - - if ( json.type === undefined ) { - - throw new Error( 'track type undefined, can not parse' ); - - } - - var trackType = KeyframeTrack._getTrackTypeForValueTypeName( json.type ); - - if ( json.times === undefined ) { - - var times = [], values = []; - - AnimationUtils.flattenJSON( json.keys, times, values, 'value' ); - - json.times = times; - json.values = values; - - } - - // derived classes can define a static parse method - if ( trackType.parse !== undefined ) { - - return trackType.parse( json ); - - } else { - - // by default, we assume a constructor compatible with the base - return new trackType( json.name, json.times, json.values, json.interpolation ); - - } - - }, - - toJSON: function ( track ) { - - var trackType = track.constructor; - - var json; - - // derived classes can define a static toJSON method - if ( trackType.toJSON !== undefined ) { - - json = trackType.toJSON( track ); - - } else { - - // by default, we assume the data can be serialized as-is - json = { - - 'name': track.name, - 'times': AnimationUtils.convertArray( track.times, Array ), - 'values': AnimationUtils.convertArray( track.values, Array ) - - }; - - var interpolation = track.getInterpolation(); - - if ( interpolation !== track.DefaultInterpolation ) { - - json.interpolation = interpolation; - - } - - } - - json.type = track.ValueTypeName; // mandatory - - return json; - - }, - - _getTrackTypeForValueTypeName: function ( typeName ) { - - switch ( typeName.toLowerCase() ) { - - case 'scalar': - case 'double': - case 'float': - case 'number': - case 'integer': - - return NumberKeyframeTrack; - - case 'vector': - case 'vector2': - case 'vector3': - case 'vector4': - - return VectorKeyframeTrack; - - case 'color': - - return ColorKeyframeTrack; - - case 'quaternion': - - return QuaternionKeyframeTrack; - - case 'bool': - case 'boolean': - - return BooleanKeyframeTrack; - - case 'string': - - return StringKeyframeTrack; - - } - - throw new Error( 'Unsupported typeName: ' + typeName ); - - } - -} ); - -/** - * - * Reusable set of Tracks that represent an animation. - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - */ - -function AnimationClip( name, duration, tracks ) { - - this.name = name; - this.tracks = tracks; - this.duration = ( duration !== undefined ) ? duration : - 1; - - this.uuid = _Math.generateUUID(); - - // this means it should figure out its duration by scanning the tracks - if ( this.duration < 0 ) { - - this.resetDuration(); - - } - - this.optimize(); - -} - -Object.assign( AnimationClip, { - - parse: function ( json ) { - - var tracks = [], - jsonTracks = json.tracks, - frameTime = 1.0 / ( json.fps || 1.0 ); - - for ( var i = 0, n = jsonTracks.length; i !== n; ++ i ) { - - tracks.push( KeyframeTrack.parse( jsonTracks[ i ] ).scale( frameTime ) ); - - } - - return new AnimationClip( json.name, json.duration, tracks ); - - }, - - toJSON: function ( clip ) { - - var tracks = [], - clipTracks = clip.tracks; - - var json = { - - 'name': clip.name, - 'duration': clip.duration, - 'tracks': tracks - - }; - - for ( var i = 0, n = clipTracks.length; i !== n; ++ i ) { - - tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) ); - - } - - return json; - - }, - - CreateFromMorphTargetSequence: function ( name, morphTargetSequence, fps, noLoop ) { - - var numMorphTargets = morphTargetSequence.length; - var tracks = []; - - for ( var i = 0; i < numMorphTargets; i ++ ) { - - var times = []; - var values = []; - - times.push( - ( i + numMorphTargets - 1 ) % numMorphTargets, - i, - ( i + 1 ) % numMorphTargets ); - - values.push( 0, 1, 0 ); - - var order = AnimationUtils.getKeyframeOrder( times ); - times = AnimationUtils.sortedArray( times, 1, order ); - values = AnimationUtils.sortedArray( values, 1, order ); - - // if there is a key at the first frame, duplicate it as the - // last frame as well for perfect loop. - if ( ! noLoop && times[ 0 ] === 0 ) { - - times.push( numMorphTargets ); - values.push( values[ 0 ] ); - - } - - tracks.push( - new NumberKeyframeTrack( - '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']', - times, values - ).scale( 1.0 / fps ) ); - - } - - return new AnimationClip( name, - 1, tracks ); - - }, - - findByName: function ( objectOrClipArray, name ) { - - var clipArray = objectOrClipArray; - - if ( ! Array.isArray( objectOrClipArray ) ) { - - var o = objectOrClipArray; - clipArray = o.geometry && o.geometry.animations || o.animations; - - } - - for ( var i = 0; i < clipArray.length; i ++ ) { - - if ( clipArray[ i ].name === name ) { - - return clipArray[ i ]; - - } - - } - - return null; - - }, - - CreateClipsFromMorphTargetSequences: function ( morphTargets, fps, noLoop ) { - - var animationToMorphTargets = {}; - - // tested with https://regex101.com/ on trick sequences - // such flamingo_flyA_003, flamingo_run1_003, crdeath0059 - var pattern = /^([\w-]*?)([\d]+)$/; - - // sort morph target names into animation groups based - // patterns like Walk_001, Walk_002, Run_001, Run_002 - for ( var i = 0, il = morphTargets.length; i < il; i ++ ) { - - var morphTarget = morphTargets[ i ]; - var parts = morphTarget.name.match( pattern ); - - if ( parts && parts.length > 1 ) { - - var name = parts[ 1 ]; - - var animationMorphTargets = animationToMorphTargets[ name ]; - if ( ! animationMorphTargets ) { - - animationToMorphTargets[ name ] = animationMorphTargets = []; - - } - - animationMorphTargets.push( morphTarget ); - - } - - } - - var clips = []; - - for ( var name in animationToMorphTargets ) { - - clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) ); - - } - - return clips; - - }, - - // parse the animation.hierarchy format - parseAnimation: function ( animation, bones ) { - - if ( ! animation ) { - - console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' ); - return null; - - } - - var addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) { - - // only return track if there are actually keys. - if ( animationKeys.length !== 0 ) { - - var times = []; - var values = []; - - AnimationUtils.flattenJSON( animationKeys, times, values, propertyName ); - - // empty keys are filtered out, so check again - if ( times.length !== 0 ) { - - destTracks.push( new trackType( trackName, times, values ) ); - - } - - } - - }; - - var tracks = []; - - var clipName = animation.name || 'default'; - // automatic length determination in AnimationClip. - var duration = animation.length || - 1; - var fps = animation.fps || 30; - - var hierarchyTracks = animation.hierarchy || []; - - for ( var h = 0; h < hierarchyTracks.length; h ++ ) { - - var animationKeys = hierarchyTracks[ h ].keys; - - // skip empty tracks - if ( ! animationKeys || animationKeys.length === 0 ) continue; - - // process morph targets - if ( animationKeys[ 0 ].morphTargets ) { - - // figure out all morph targets used in this track - var morphTargetNames = {}; - - for ( var k = 0; k < animationKeys.length; k ++ ) { - - if ( animationKeys[ k ].morphTargets ) { - - for ( var m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) { - - morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1; - - } - - } - - } - - // create a track for each morph target with all zero - // morphTargetInfluences except for the keys in which - // the morphTarget is named. - for ( var morphTargetName in morphTargetNames ) { - - var times = []; - var values = []; - - for ( var m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) { - - var animationKey = animationKeys[ k ]; - - times.push( animationKey.time ); - values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 ); - - } - - tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) ); - - } - - duration = morphTargetNames.length * ( fps || 1.0 ); - - } else { - - // ...assume skeletal animation - - var boneName = '.bones[' + bones[ h ].name + ']'; - - addNonemptyTrack( - VectorKeyframeTrack, boneName + '.position', - animationKeys, 'pos', tracks ); - - addNonemptyTrack( - QuaternionKeyframeTrack, boneName + '.quaternion', - animationKeys, 'rot', tracks ); - - addNonemptyTrack( - VectorKeyframeTrack, boneName + '.scale', - animationKeys, 'scl', tracks ); - - } - - } - - if ( tracks.length === 0 ) { - - return null; - - } - - var clip = new AnimationClip( clipName, duration, tracks ); - - return clip; - - } - -} ); - -Object.assign( AnimationClip.prototype, { - - resetDuration: function () { - - var tracks = this.tracks, duration = 0; - - for ( var i = 0, n = tracks.length; i !== n; ++ i ) { - - var track = this.tracks[ i ]; - - duration = Math.max( duration, track.times[ track.times.length - 1 ] ); - - } - - this.duration = duration; - - }, - - trim: function () { - - for ( var i = 0; i < this.tracks.length; i ++ ) { - - this.tracks[ i ].trim( 0, this.duration ); - - } - - return this; - - }, - - optimize: function () { - - for ( var i = 0; i < this.tracks.length; i ++ ) { - - this.tracks[ i ].optimize(); - - } - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function MaterialLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - this.textures = {}; - -} - -Object.assign( MaterialLoader.prototype, { - - load: function ( url, onLoad, onProgress, onError ) { - - var scope = this; - - var loader = new FileLoader( scope.manager ); - loader.load( url, function ( text ) { - - onLoad( scope.parse( JSON.parse( text ) ) ); - - }, onProgress, onError ); - - }, - - setTextures: function ( value ) { - - this.textures = value; - - }, - - parse: function ( json ) { - - var textures = this.textures; - - function getTexture( name ) { - - if ( textures[ name ] === undefined ) { - - console.warn( 'THREE.MaterialLoader: Undefined texture', name ); - - } - - return textures[ name ]; - - } - - var material = new Materials[ json.type ](); - - if ( json.uuid !== undefined ) material.uuid = json.uuid; - if ( json.name !== undefined ) material.name = json.name; - if ( json.color !== undefined ) material.color.setHex( json.color ); - if ( json.roughness !== undefined ) material.roughness = json.roughness; - if ( json.metalness !== undefined ) material.metalness = json.metalness; - if ( json.emissive !== undefined ) material.emissive.setHex( json.emissive ); - if ( json.specular !== undefined ) material.specular.setHex( json.specular ); - if ( json.shininess !== undefined ) material.shininess = json.shininess; - if ( json.clearCoat !== undefined ) material.clearCoat = json.clearCoat; - if ( json.clearCoatRoughness !== undefined ) material.clearCoatRoughness = json.clearCoatRoughness; - if ( json.uniforms !== undefined ) material.uniforms = json.uniforms; - if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader; - if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader; - if ( json.vertexColors !== undefined ) material.vertexColors = json.vertexColors; - if ( json.fog !== undefined ) material.fog = json.fog; - if ( json.flatShading !== undefined ) material.flatShading = json.flatShading; - if ( json.blending !== undefined ) material.blending = json.blending; - if ( json.side !== undefined ) material.side = json.side; - if ( json.opacity !== undefined ) material.opacity = json.opacity; - if ( json.transparent !== undefined ) material.transparent = json.transparent; - if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest; - if ( json.depthTest !== undefined ) material.depthTest = json.depthTest; - if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite; - if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite; - if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; - if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth; - if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap; - if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin; - - if ( json.rotation !== undefined ) material.rotation = json.rotation; - - if ( json.linewidth !== 1 ) material.linewidth = json.linewidth; - if ( json.dashSize !== undefined ) material.dashSize = json.dashSize; - if ( json.gapSize !== undefined ) material.gapSize = json.gapSize; - if ( json.scale !== undefined ) material.scale = json.scale; - - if ( json.skinning !== undefined ) material.skinning = json.skinning; - if ( json.morphTargets !== undefined ) material.morphTargets = json.morphTargets; - if ( json.dithering !== undefined ) material.dithering = json.dithering; - - if ( json.visible !== undefined ) material.visible = json.visible; - if ( json.userData !== undefined ) material.userData = json.userData; - - // Deprecated - - if ( json.shading !== undefined ) material.flatShading = json.shading === 1; // THREE.FlatShading - - // for PointsMaterial - - if ( json.size !== undefined ) material.size = json.size; - if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation; - - // maps - - if ( json.map !== undefined ) material.map = getTexture( json.map ); - - if ( json.alphaMap !== undefined ) { - - material.alphaMap = getTexture( json.alphaMap ); - material.transparent = true; - - } - - if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap ); - if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale; - - if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap ); - if ( json.normalScale !== undefined ) { - - var normalScale = json.normalScale; - - if ( Array.isArray( normalScale ) === false ) { - - // Blender exporter used to export a scalar. See #7459 - - normalScale = [ normalScale, normalScale ]; - - } - - material.normalScale = new Vector2().fromArray( normalScale ); - - } - - if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap ); - if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale; - if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias; - - if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap ); - if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap ); - - if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap ); - if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity; - - if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap ); - - if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap ); - - if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity; - - if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap ); - if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity; - - if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap ); - if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity; - - if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap ); - - return material; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function BufferGeometryLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - -} - -Object.assign( BufferGeometryLoader.prototype, { - - load: function ( url, onLoad, onProgress, onError ) { - - var scope = this; - - var loader = new FileLoader( scope.manager ); - loader.load( url, function ( text ) { - - onLoad( scope.parse( JSON.parse( text ) ) ); - - }, onProgress, onError ); - - }, - - parse: function ( json ) { - - var geometry = new BufferGeometry(); - - var index = json.data.index; - - if ( index !== undefined ) { - - var typedArray = new TYPED_ARRAYS[ index.type ]( index.array ); - geometry.setIndex( new BufferAttribute( typedArray, 1 ) ); - - } - - var attributes = json.data.attributes; - - for ( var key in attributes ) { - - var attribute = attributes[ key ]; - var typedArray = new TYPED_ARRAYS[ attribute.type ]( attribute.array ); - - geometry.addAttribute( key, new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized ) ); - - } - - var groups = json.data.groups || json.data.drawcalls || json.data.offsets; - - if ( groups !== undefined ) { - - for ( var i = 0, n = groups.length; i !== n; ++ i ) { - - var group = groups[ i ]; - - geometry.addGroup( group.start, group.count, group.materialIndex ); - - } - - } - - var boundingSphere = json.data.boundingSphere; - - if ( boundingSphere !== undefined ) { - - var center = new Vector3(); - - if ( boundingSphere.center !== undefined ) { - - center.fromArray( boundingSphere.center ); - - } - - geometry.boundingSphere = new Sphere( center, boundingSphere.radius ); - - } - - return geometry; - - } - -} ); - -var TYPED_ARRAYS = { - Int8Array: Int8Array, - Uint8Array: Uint8Array, - // Workaround for IE11 pre KB2929437. See #11440 - Uint8ClampedArray: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : Uint8Array, - Int16Array: Int16Array, - Uint16Array: Uint16Array, - Int32Array: Int32Array, - Uint32Array: Uint32Array, - Float32Array: Float32Array, - Float64Array: Float64Array -}; - -/** - * @author alteredq / http://alteredqualia.com/ - */ - -function Loader() { - - this.onLoadStart = function () {}; - this.onLoadProgress = function () {}; - this.onLoadComplete = function () {}; - -} - -Loader.Handlers = { - - handlers: [], - - add: function ( regex, loader ) { - - this.handlers.push( regex, loader ); - - }, - - get: function ( file ) { - - var handlers = this.handlers; - - for ( var i = 0, l = handlers.length; i < l; i += 2 ) { - - var regex = handlers[ i ]; - var loader = handlers[ i + 1 ]; - - if ( regex.test( file ) ) { - - return loader; - - } - - } - - return null; - - } - -}; - -Object.assign( Loader.prototype, { - - crossOrigin: undefined, - - extractUrlBase: function ( url ) { - - var parts = url.split( '/' ); - - if ( parts.length === 1 ) return './'; - - parts.pop(); - - return parts.join( '/' ) + '/'; - - }, - - initMaterials: function ( materials, texturePath, crossOrigin ) { - - var array = []; - - for ( var i = 0; i < materials.length; ++ i ) { - - array[ i ] = this.createMaterial( materials[ i ], texturePath, crossOrigin ); - - } - - return array; - - }, - - createMaterial: ( function () { - - var BlendingMode = { - NoBlending: NoBlending, - NormalBlending: NormalBlending, - AdditiveBlending: AdditiveBlending, - SubtractiveBlending: SubtractiveBlending, - MultiplyBlending: MultiplyBlending, - CustomBlending: CustomBlending - }; - - var color = new Color(); - var textureLoader = new TextureLoader(); - var materialLoader = new MaterialLoader(); - - return function createMaterial( m, texturePath, crossOrigin ) { - - // convert from old material format - - var textures = {}; - - function loadTexture( path, repeat, offset, wrap, anisotropy ) { - - var fullPath = texturePath + path; - var loader = Loader.Handlers.get( fullPath ); - - var texture; - - if ( loader !== null ) { - - texture = loader.load( fullPath ); - - } else { - - textureLoader.setCrossOrigin( crossOrigin ); - texture = textureLoader.load( fullPath ); - - } - - if ( repeat !== undefined ) { - - texture.repeat.fromArray( repeat ); - - if ( repeat[ 0 ] !== 1 ) texture.wrapS = RepeatWrapping; - if ( repeat[ 1 ] !== 1 ) texture.wrapT = RepeatWrapping; - - } - - if ( offset !== undefined ) { - - texture.offset.fromArray( offset ); - - } - - if ( wrap !== undefined ) { - - if ( wrap[ 0 ] === 'repeat' ) texture.wrapS = RepeatWrapping; - if ( wrap[ 0 ] === 'mirror' ) texture.wrapS = MirroredRepeatWrapping; - - if ( wrap[ 1 ] === 'repeat' ) texture.wrapT = RepeatWrapping; - if ( wrap[ 1 ] === 'mirror' ) texture.wrapT = MirroredRepeatWrapping; - - } - - if ( anisotropy !== undefined ) { - - texture.anisotropy = anisotropy; - - } - - var uuid = _Math.generateUUID(); - - textures[ uuid ] = texture; - - return uuid; - - } - - // - - var json = { - uuid: _Math.generateUUID(), - type: 'MeshLambertMaterial' - }; - - for ( var name in m ) { - - var value = m[ name ]; - - switch ( name ) { - - case 'DbgColor': - case 'DbgIndex': - case 'opticalDensity': - case 'illumination': - break; - case 'DbgName': - json.name = value; - break; - case 'blending': - json.blending = BlendingMode[ value ]; - break; - case 'colorAmbient': - case 'mapAmbient': - console.warn( 'THREE.Loader.createMaterial:', name, 'is no longer supported.' ); - break; - case 'colorDiffuse': - json.color = color.fromArray( value ).getHex(); - break; - case 'colorSpecular': - json.specular = color.fromArray( value ).getHex(); - break; - case 'colorEmissive': - json.emissive = color.fromArray( value ).getHex(); - break; - case 'specularCoef': - json.shininess = value; - break; - case 'shading': - if ( value.toLowerCase() === 'basic' ) json.type = 'MeshBasicMaterial'; - if ( value.toLowerCase() === 'phong' ) json.type = 'MeshPhongMaterial'; - if ( value.toLowerCase() === 'standard' ) json.type = 'MeshStandardMaterial'; - break; - case 'mapDiffuse': - json.map = loadTexture( value, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy ); - break; - case 'mapDiffuseRepeat': - case 'mapDiffuseOffset': - case 'mapDiffuseWrap': - case 'mapDiffuseAnisotropy': - break; - case 'mapEmissive': - json.emissiveMap = loadTexture( value, m.mapEmissiveRepeat, m.mapEmissiveOffset, m.mapEmissiveWrap, m.mapEmissiveAnisotropy ); - break; - case 'mapEmissiveRepeat': - case 'mapEmissiveOffset': - case 'mapEmissiveWrap': - case 'mapEmissiveAnisotropy': - break; - case 'mapLight': - json.lightMap = loadTexture( value, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy ); - break; - case 'mapLightRepeat': - case 'mapLightOffset': - case 'mapLightWrap': - case 'mapLightAnisotropy': - break; - case 'mapAO': - json.aoMap = loadTexture( value, m.mapAORepeat, m.mapAOOffset, m.mapAOWrap, m.mapAOAnisotropy ); - break; - case 'mapAORepeat': - case 'mapAOOffset': - case 'mapAOWrap': - case 'mapAOAnisotropy': - break; - case 'mapBump': - json.bumpMap = loadTexture( value, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy ); - break; - case 'mapBumpScale': - json.bumpScale = value; - break; - case 'mapBumpRepeat': - case 'mapBumpOffset': - case 'mapBumpWrap': - case 'mapBumpAnisotropy': - break; - case 'mapNormal': - json.normalMap = loadTexture( value, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy ); - break; - case 'mapNormalFactor': - json.normalScale = [ value, value ]; - break; - case 'mapNormalRepeat': - case 'mapNormalOffset': - case 'mapNormalWrap': - case 'mapNormalAnisotropy': - break; - case 'mapSpecular': - json.specularMap = loadTexture( value, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy ); - break; - case 'mapSpecularRepeat': - case 'mapSpecularOffset': - case 'mapSpecularWrap': - case 'mapSpecularAnisotropy': - break; - case 'mapMetalness': - json.metalnessMap = loadTexture( value, m.mapMetalnessRepeat, m.mapMetalnessOffset, m.mapMetalnessWrap, m.mapMetalnessAnisotropy ); - break; - case 'mapMetalnessRepeat': - case 'mapMetalnessOffset': - case 'mapMetalnessWrap': - case 'mapMetalnessAnisotropy': - break; - case 'mapRoughness': - json.roughnessMap = loadTexture( value, m.mapRoughnessRepeat, m.mapRoughnessOffset, m.mapRoughnessWrap, m.mapRoughnessAnisotropy ); - break; - case 'mapRoughnessRepeat': - case 'mapRoughnessOffset': - case 'mapRoughnessWrap': - case 'mapRoughnessAnisotropy': - break; - case 'mapAlpha': - json.alphaMap = loadTexture( value, m.mapAlphaRepeat, m.mapAlphaOffset, m.mapAlphaWrap, m.mapAlphaAnisotropy ); - break; - case 'mapAlphaRepeat': - case 'mapAlphaOffset': - case 'mapAlphaWrap': - case 'mapAlphaAnisotropy': - break; - case 'flipSided': - json.side = BackSide; - break; - case 'doubleSided': - json.side = DoubleSide; - break; - case 'transparency': - console.warn( 'THREE.Loader.createMaterial: transparency has been renamed to opacity' ); - json.opacity = value; - break; - case 'depthTest': - case 'depthWrite': - case 'colorWrite': - case 'opacity': - case 'reflectivity': - case 'transparent': - case 'visible': - case 'wireframe': - json[ name ] = value; - break; - case 'vertexColors': - if ( value === true ) json.vertexColors = VertexColors; - if ( value === 'face' ) json.vertexColors = FaceColors; - break; - default: - console.error( 'THREE.Loader.createMaterial: Unsupported', name, value ); - break; - - } - - } - - if ( json.type === 'MeshBasicMaterial' ) delete json.emissive; - if ( json.type !== 'MeshPhongMaterial' ) delete json.specular; - - if ( json.opacity < 1 ) json.transparent = true; - - materialLoader.setTextures( textures ); - - return materialLoader.parse( json ); - - }; - - } )() - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author alteredq / http://alteredqualia.com/ - */ - -function JSONLoader( manager ) { - - if ( typeof manager === 'boolean' ) { - - console.warn( 'THREE.JSONLoader: showStatus parameter has been removed from constructor.' ); - manager = undefined; - - } - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - - this.withCredentials = false; - -} - -Object.assign( JSONLoader.prototype, { - - load: function ( url, onLoad, onProgress, onError ) { - - var scope = this; - - var texturePath = this.texturePath && ( typeof this.texturePath === "string" ) ? this.texturePath : Loader.prototype.extractUrlBase( url ); - - var loader = new FileLoader( this.manager ); - loader.setWithCredentials( this.withCredentials ); - loader.load( url, function ( text ) { - - var json = JSON.parse( text ); - var metadata = json.metadata; - - if ( metadata !== undefined ) { - - var type = metadata.type; - - if ( type !== undefined ) { - - if ( type.toLowerCase() === 'object' ) { - - console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.ObjectLoader instead.' ); - return; - - } - - if ( type.toLowerCase() === 'scene' ) { - - console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.SceneLoader instead.' ); - return; - - } - - } - - } - - var object = scope.parse( json, texturePath ); - onLoad( object.geometry, object.materials ); - - }, onProgress, onError ); - - }, - - setTexturePath: function ( value ) { - - this.texturePath = value; - - }, - - parse: ( function () { - - function parseModel( json, geometry ) { - - function isBitSet( value, position ) { - - return value & ( 1 << position ); - - } - - var i, j, fi, - - offset, zLength, - - colorIndex, normalIndex, uvIndex, materialIndex, - - type, - isQuad, - hasMaterial, - hasFaceVertexUv, - hasFaceNormal, hasFaceVertexNormal, - hasFaceColor, hasFaceVertexColor, - - vertex, face, faceA, faceB, hex, normal, - - uvLayer, uv, u, v, - - faces = json.faces, - vertices = json.vertices, - normals = json.normals, - colors = json.colors, - - scale = json.scale, - - nUvLayers = 0; - - - if ( json.uvs !== undefined ) { - - // disregard empty arrays - - for ( i = 0; i < json.uvs.length; i ++ ) { - - if ( json.uvs[ i ].length ) nUvLayers ++; - - } - - for ( i = 0; i < nUvLayers; i ++ ) { - - geometry.faceVertexUvs[ i ] = []; - - } - - } - - offset = 0; - zLength = vertices.length; - - while ( offset < zLength ) { - - vertex = new Vector3(); - - vertex.x = vertices[ offset ++ ] * scale; - vertex.y = vertices[ offset ++ ] * scale; - vertex.z = vertices[ offset ++ ] * scale; - - geometry.vertices.push( vertex ); - - } - - offset = 0; - zLength = faces.length; - - while ( offset < zLength ) { - - type = faces[ offset ++ ]; - - isQuad = isBitSet( type, 0 ); - hasMaterial = isBitSet( type, 1 ); - hasFaceVertexUv = isBitSet( type, 3 ); - hasFaceNormal = isBitSet( type, 4 ); - hasFaceVertexNormal = isBitSet( type, 5 ); - hasFaceColor = isBitSet( type, 6 ); - hasFaceVertexColor = isBitSet( type, 7 ); - - // console.log("type", type, "bits", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor); - - if ( isQuad ) { - - faceA = new Face3(); - faceA.a = faces[ offset ]; - faceA.b = faces[ offset + 1 ]; - faceA.c = faces[ offset + 3 ]; - - faceB = new Face3(); - faceB.a = faces[ offset + 1 ]; - faceB.b = faces[ offset + 2 ]; - faceB.c = faces[ offset + 3 ]; - - offset += 4; - - if ( hasMaterial ) { - - materialIndex = faces[ offset ++ ]; - faceA.materialIndex = materialIndex; - faceB.materialIndex = materialIndex; - - } - - // to get face <=> uv index correspondence - - fi = geometry.faces.length; - - if ( hasFaceVertexUv ) { - - for ( i = 0; i < nUvLayers; i ++ ) { - - uvLayer = json.uvs[ i ]; - - geometry.faceVertexUvs[ i ][ fi ] = []; - geometry.faceVertexUvs[ i ][ fi + 1 ] = []; - - for ( j = 0; j < 4; j ++ ) { - - uvIndex = faces[ offset ++ ]; - - u = uvLayer[ uvIndex * 2 ]; - v = uvLayer[ uvIndex * 2 + 1 ]; - - uv = new Vector2( u, v ); - - if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv ); - if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv ); - - } - - } - - } - - if ( hasFaceNormal ) { - - normalIndex = faces[ offset ++ ] * 3; - - faceA.normal.set( - normals[ normalIndex ++ ], - normals[ normalIndex ++ ], - normals[ normalIndex ] - ); - - faceB.normal.copy( faceA.normal ); - - } - - if ( hasFaceVertexNormal ) { - - for ( i = 0; i < 4; i ++ ) { - - normalIndex = faces[ offset ++ ] * 3; - - normal = new Vector3( - normals[ normalIndex ++ ], - normals[ normalIndex ++ ], - normals[ normalIndex ] - ); - - - if ( i !== 2 ) faceA.vertexNormals.push( normal ); - if ( i !== 0 ) faceB.vertexNormals.push( normal ); - - } - - } - - - if ( hasFaceColor ) { - - colorIndex = faces[ offset ++ ]; - hex = colors[ colorIndex ]; - - faceA.color.setHex( hex ); - faceB.color.setHex( hex ); - - } - - - if ( hasFaceVertexColor ) { - - for ( i = 0; i < 4; i ++ ) { - - colorIndex = faces[ offset ++ ]; - hex = colors[ colorIndex ]; - - if ( i !== 2 ) faceA.vertexColors.push( new Color( hex ) ); - if ( i !== 0 ) faceB.vertexColors.push( new Color( hex ) ); - - } - - } - - geometry.faces.push( faceA ); - geometry.faces.push( faceB ); - - } else { - - face = new Face3(); - face.a = faces[ offset ++ ]; - face.b = faces[ offset ++ ]; - face.c = faces[ offset ++ ]; - - if ( hasMaterial ) { - - materialIndex = faces[ offset ++ ]; - face.materialIndex = materialIndex; - - } - - // to get face <=> uv index correspondence - - fi = geometry.faces.length; - - if ( hasFaceVertexUv ) { - - for ( i = 0; i < nUvLayers; i ++ ) { - - uvLayer = json.uvs[ i ]; - - geometry.faceVertexUvs[ i ][ fi ] = []; - - for ( j = 0; j < 3; j ++ ) { - - uvIndex = faces[ offset ++ ]; - - u = uvLayer[ uvIndex * 2 ]; - v = uvLayer[ uvIndex * 2 + 1 ]; - - uv = new Vector2( u, v ); - - geometry.faceVertexUvs[ i ][ fi ].push( uv ); - - } - - } - - } - - if ( hasFaceNormal ) { - - normalIndex = faces[ offset ++ ] * 3; - - face.normal.set( - normals[ normalIndex ++ ], - normals[ normalIndex ++ ], - normals[ normalIndex ] - ); - - } - - if ( hasFaceVertexNormal ) { - - for ( i = 0; i < 3; i ++ ) { - - normalIndex = faces[ offset ++ ] * 3; - - normal = new Vector3( - normals[ normalIndex ++ ], - normals[ normalIndex ++ ], - normals[ normalIndex ] - ); - - face.vertexNormals.push( normal ); - - } - - } - - - if ( hasFaceColor ) { - - colorIndex = faces[ offset ++ ]; - face.color.setHex( colors[ colorIndex ] ); - - } - - - if ( hasFaceVertexColor ) { - - for ( i = 0; i < 3; i ++ ) { - - colorIndex = faces[ offset ++ ]; - face.vertexColors.push( new Color( colors[ colorIndex ] ) ); - - } - - } - - geometry.faces.push( face ); - - } - - } - - } - - function parseSkin( json, geometry ) { - - var influencesPerVertex = ( json.influencesPerVertex !== undefined ) ? json.influencesPerVertex : 2; - - if ( json.skinWeights ) { - - for ( var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex ) { - - var x = json.skinWeights[ i ]; - var y = ( influencesPerVertex > 1 ) ? json.skinWeights[ i + 1 ] : 0; - var z = ( influencesPerVertex > 2 ) ? json.skinWeights[ i + 2 ] : 0; - var w = ( influencesPerVertex > 3 ) ? json.skinWeights[ i + 3 ] : 0; - - geometry.skinWeights.push( new Vector4( x, y, z, w ) ); - - } - - } - - if ( json.skinIndices ) { - - for ( var i = 0, l = json.skinIndices.length; i < l; i += influencesPerVertex ) { - - var a = json.skinIndices[ i ]; - var b = ( influencesPerVertex > 1 ) ? json.skinIndices[ i + 1 ] : 0; - var c = ( influencesPerVertex > 2 ) ? json.skinIndices[ i + 2 ] : 0; - var d = ( influencesPerVertex > 3 ) ? json.skinIndices[ i + 3 ] : 0; - - geometry.skinIndices.push( new Vector4( a, b, c, d ) ); - - } - - } - - geometry.bones = json.bones; - - if ( geometry.bones && geometry.bones.length > 0 && ( geometry.skinWeights.length !== geometry.skinIndices.length || geometry.skinIndices.length !== geometry.vertices.length ) ) { - - console.warn( 'When skinning, number of vertices (' + geometry.vertices.length + '), skinIndices (' + - geometry.skinIndices.length + '), and skinWeights (' + geometry.skinWeights.length + ') should match.' ); - - } - - } - - function parseMorphing( json, geometry ) { - - var scale = json.scale; - - if ( json.morphTargets !== undefined ) { - - for ( var i = 0, l = json.morphTargets.length; i < l; i ++ ) { - - geometry.morphTargets[ i ] = {}; - geometry.morphTargets[ i ].name = json.morphTargets[ i ].name; - geometry.morphTargets[ i ].vertices = []; - - var dstVertices = geometry.morphTargets[ i ].vertices; - var srcVertices = json.morphTargets[ i ].vertices; - - for ( var v = 0, vl = srcVertices.length; v < vl; v += 3 ) { - - var vertex = new Vector3(); - vertex.x = srcVertices[ v ] * scale; - vertex.y = srcVertices[ v + 1 ] * scale; - vertex.z = srcVertices[ v + 2 ] * scale; - - dstVertices.push( vertex ); - - } - - } - - } - - if ( json.morphColors !== undefined && json.morphColors.length > 0 ) { - - console.warn( 'THREE.JSONLoader: "morphColors" no longer supported. Using them as face colors.' ); - - var faces = geometry.faces; - var morphColors = json.morphColors[ 0 ].colors; - - for ( var i = 0, l = faces.length; i < l; i ++ ) { - - faces[ i ].color.fromArray( morphColors, i * 3 ); - - } - - } - - } - - function parseAnimations( json, geometry ) { - - var outputAnimations = []; - - // parse old style Bone/Hierarchy animations - var animations = []; - - if ( json.animation !== undefined ) { - - animations.push( json.animation ); - - } - - if ( json.animations !== undefined ) { - - if ( json.animations.length ) { - - animations = animations.concat( json.animations ); - - } else { - - animations.push( json.animations ); - - } - - } - - for ( var i = 0; i < animations.length; i ++ ) { - - var clip = AnimationClip.parseAnimation( animations[ i ], geometry.bones ); - if ( clip ) outputAnimations.push( clip ); - - } - - // parse implicit morph animations - if ( geometry.morphTargets ) { - - // TODO: Figure out what an appropraite FPS is for morph target animations -- defaulting to 10, but really it is completely arbitrary. - var morphAnimationClips = AnimationClip.CreateClipsFromMorphTargetSequences( geometry.morphTargets, 10 ); - outputAnimations = outputAnimations.concat( morphAnimationClips ); - - } - - if ( outputAnimations.length > 0 ) geometry.animations = outputAnimations; - - } - - return function ( json, texturePath ) { - - if ( json.data !== undefined ) { - - // Geometry 4.0 spec - json = json.data; - - } - - if ( json.scale !== undefined ) { - - json.scale = 1.0 / json.scale; - - } else { - - json.scale = 1.0; - - } - - var geometry = new Geometry(); - - parseModel( json, geometry ); - parseSkin( json, geometry ); - parseMorphing( json, geometry ); - parseAnimations( json, geometry ); - - geometry.computeFaceNormals(); - geometry.computeBoundingSphere(); - - if ( json.materials === undefined || json.materials.length === 0 ) { - - return { geometry: geometry }; - - } else { - - var materials = Loader.prototype.initMaterials( json.materials, texturePath, this.crossOrigin ); - - return { geometry: geometry, materials: materials }; - - } - - }; - - } )() - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function ObjectLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - this.texturePath = ''; - -} - -Object.assign( ObjectLoader.prototype, { - - load: function ( url, onLoad, onProgress, onError ) { - - if ( this.texturePath === '' ) { - - this.texturePath = url.substring( 0, url.lastIndexOf( '/' ) + 1 ); - - } - - var scope = this; - - var loader = new FileLoader( scope.manager ); - loader.load( url, function ( text ) { - - var json = null; - - try { - - json = JSON.parse( text ); - - } catch ( error ) { - - if ( onError !== undefined ) onError( error ); - - console.error( 'THREE:ObjectLoader: Can\'t parse ' + url + '.', error.message ); - - return; - - } - - var metadata = json.metadata; - - if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { - - console.error( 'THREE.ObjectLoader: Can\'t load ' + url + '. Use THREE.JSONLoader instead.' ); - return; - - } - - scope.parse( json, onLoad ); - - }, onProgress, onError ); - - }, - - setTexturePath: function ( value ) { - - this.texturePath = value; - - }, - - setCrossOrigin: function ( value ) { - - this.crossOrigin = value; - - }, - - parse: function ( json, onLoad ) { - - var geometries = this.parseGeometries( json.geometries ); - - var images = this.parseImages( json.images, function () { - - if ( onLoad !== undefined ) onLoad( object ); - - } ); - - var textures = this.parseTextures( json.textures, images ); - var materials = this.parseMaterials( json.materials, textures ); - - var object = this.parseObject( json.object, geometries, materials ); - - if ( json.animations ) { - - object.animations = this.parseAnimations( json.animations ); - - } - - if ( json.images === undefined || json.images.length === 0 ) { - - if ( onLoad !== undefined ) onLoad( object ); - - } - - return object; - - }, - - parseGeometries: function ( json ) { - - var geometries = {}; - - if ( json !== undefined ) { - - var geometryLoader = new JSONLoader(); - var bufferGeometryLoader = new BufferGeometryLoader(); - - for ( var i = 0, l = json.length; i < l; i ++ ) { - - var geometry; - var data = json[ i ]; - - switch ( data.type ) { - - case 'PlaneGeometry': - case 'PlaneBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.width, - data.height, - data.widthSegments, - data.heightSegments - ); - - break; - - case 'BoxGeometry': - case 'BoxBufferGeometry': - case 'CubeGeometry': // backwards compatible - - geometry = new Geometries[ data.type ]( - data.width, - data.height, - data.depth, - data.widthSegments, - data.heightSegments, - data.depthSegments - ); - - break; - - case 'CircleGeometry': - case 'CircleBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.radius, - data.segments, - data.thetaStart, - data.thetaLength - ); - - break; - - case 'CylinderGeometry': - case 'CylinderBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.radiusTop, - data.radiusBottom, - data.height, - data.radialSegments, - data.heightSegments, - data.openEnded, - data.thetaStart, - data.thetaLength - ); - - break; - - case 'ConeGeometry': - case 'ConeBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.radius, - data.height, - data.radialSegments, - data.heightSegments, - data.openEnded, - data.thetaStart, - data.thetaLength - ); - - break; - - case 'SphereGeometry': - case 'SphereBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.radius, - data.widthSegments, - data.heightSegments, - data.phiStart, - data.phiLength, - data.thetaStart, - data.thetaLength - ); - - break; - - case 'DodecahedronGeometry': - case 'DodecahedronBufferGeometry': - case 'IcosahedronGeometry': - case 'IcosahedronBufferGeometry': - case 'OctahedronGeometry': - case 'OctahedronBufferGeometry': - case 'TetrahedronGeometry': - case 'TetrahedronBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.radius, - data.detail - ); - - break; - - case 'RingGeometry': - case 'RingBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.innerRadius, - data.outerRadius, - data.thetaSegments, - data.phiSegments, - data.thetaStart, - data.thetaLength - ); - - break; - - case 'TorusGeometry': - case 'TorusBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.radius, - data.tube, - data.radialSegments, - data.tubularSegments, - data.arc - ); - - break; - - case 'TorusKnotGeometry': - case 'TorusKnotBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.radius, - data.tube, - data.tubularSegments, - data.radialSegments, - data.p, - data.q - ); - - break; - - case 'LatheGeometry': - case 'LatheBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.points, - data.segments, - data.phiStart, - data.phiLength - ); - - break; - - case 'PolyhedronGeometry': - case 'PolyhedronBufferGeometry': - - geometry = new Geometries[ data.type ]( - data.vertices, - data.indices, - data.radius, - data.details - ); - - break; - - case 'BufferGeometry': - - geometry = bufferGeometryLoader.parse( data ); - - break; - - case 'Geometry': - - geometry = geometryLoader.parse( data, this.texturePath ).geometry; - - break; - - default: - - console.warn( 'THREE.ObjectLoader: Unsupported geometry type "' + data.type + '"' ); - - continue; - - } - - geometry.uuid = data.uuid; - - if ( data.name !== undefined ) geometry.name = data.name; - - geometries[ data.uuid ] = geometry; - - } - - } - - return geometries; - - }, - - parseMaterials: function ( json, textures ) { - - var materials = {}; - - if ( json !== undefined ) { - - var loader = new MaterialLoader(); - loader.setTextures( textures ); - - for ( var i = 0, l = json.length; i < l; i ++ ) { - - var data = json[ i ]; - - if ( data.type === 'MultiMaterial' ) { - - // Deprecated - - var array = []; - - for ( var j = 0; j < data.materials.length; j ++ ) { - - array.push( loader.parse( data.materials[ j ] ) ); - - } - - materials[ data.uuid ] = array; - - } else { - - materials[ data.uuid ] = loader.parse( data ); - - } - - } - - } - - return materials; - - }, - - parseAnimations: function ( json ) { - - var animations = []; - - for ( var i = 0; i < json.length; i ++ ) { - - var clip = AnimationClip.parse( json[ i ] ); - - animations.push( clip ); - - } - - return animations; - - }, - - parseImages: function ( json, onLoad ) { - - var scope = this; - var images = {}; - - function loadImage( url ) { - - scope.manager.itemStart( url ); - - return loader.load( url, function () { - - scope.manager.itemEnd( url ); - - }, undefined, function () { - - scope.manager.itemEnd( url ); - scope.manager.itemError( url ); - - } ); - - } - - if ( json !== undefined && json.length > 0 ) { - - var manager = new LoadingManager( onLoad ); - - var loader = new ImageLoader( manager ); - loader.setCrossOrigin( this.crossOrigin ); - - for ( var i = 0, l = json.length; i < l; i ++ ) { - - var image = json[ i ]; - var path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( image.url ) ? image.url : scope.texturePath + image.url; - - images[ image.uuid ] = loadImage( path ); - - } - - } - - return images; - - }, - - parseTextures: function ( json, images ) { - - function parseConstant( value, type ) { - - if ( typeof value === 'number' ) return value; - - console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value ); - - return type[ value ]; - - } - - var textures = {}; - - if ( json !== undefined ) { - - for ( var i = 0, l = json.length; i < l; i ++ ) { - - var data = json[ i ]; - - if ( data.image === undefined ) { - - console.warn( 'THREE.ObjectLoader: No "image" specified for', data.uuid ); - - } - - if ( images[ data.image ] === undefined ) { - - console.warn( 'THREE.ObjectLoader: Undefined image', data.image ); - - } - - var texture = new Texture( images[ data.image ] ); - texture.needsUpdate = true; - - texture.uuid = data.uuid; - - if ( data.name !== undefined ) texture.name = data.name; - - if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping, TEXTURE_MAPPING ); - - if ( data.offset !== undefined ) texture.offset.fromArray( data.offset ); - if ( data.repeat !== undefined ) texture.repeat.fromArray( data.repeat ); - if ( data.center !== undefined ) texture.center.fromArray( data.center ); - if ( data.rotation !== undefined ) texture.rotation = data.rotation; - - if ( data.wrap !== undefined ) { - - texture.wrapS = parseConstant( data.wrap[ 0 ], TEXTURE_WRAPPING ); - texture.wrapT = parseConstant( data.wrap[ 1 ], TEXTURE_WRAPPING ); - - } - - if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter, TEXTURE_FILTER ); - if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter, TEXTURE_FILTER ); - if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy; - - if ( data.flipY !== undefined ) texture.flipY = data.flipY; - - textures[ data.uuid ] = texture; - - } - - } - - return textures; - - }, - - parseObject: function () { - - var matrix = new Matrix4(); - - return function parseObject( data, geometries, materials ) { - - var object; - - function getGeometry( name ) { - - if ( geometries[ name ] === undefined ) { - - console.warn( 'THREE.ObjectLoader: Undefined geometry', name ); - - } - - return geometries[ name ]; - - } - - function getMaterial( name ) { - - if ( name === undefined ) return undefined; - - if ( Array.isArray( name ) ) { - - var array = []; - - for ( var i = 0, l = name.length; i < l; i ++ ) { - - var uuid = name[ i ]; - - if ( materials[ uuid ] === undefined ) { - - console.warn( 'THREE.ObjectLoader: Undefined material', uuid ); - - } - - array.push( materials[ uuid ] ); - - } - - return array; - - } - - if ( materials[ name ] === undefined ) { - - console.warn( 'THREE.ObjectLoader: Undefined material', name ); - - } - - return materials[ name ]; - - } - - switch ( data.type ) { - - case 'Scene': - - object = new Scene(); - - if ( data.background !== undefined ) { - - if ( Number.isInteger( data.background ) ) { - - object.background = new Color( data.background ); - - } - - } - - if ( data.fog !== undefined ) { - - if ( data.fog.type === 'Fog' ) { - - object.fog = new Fog( data.fog.color, data.fog.near, data.fog.far ); - - } else if ( data.fog.type === 'FogExp2' ) { - - object.fog = new FogExp2( data.fog.color, data.fog.density ); - - } - - } - - break; - - case 'PerspectiveCamera': - - object = new PerspectiveCamera( data.fov, data.aspect, data.near, data.far ); - - if ( data.focus !== undefined ) object.focus = data.focus; - if ( data.zoom !== undefined ) object.zoom = data.zoom; - if ( data.filmGauge !== undefined ) object.filmGauge = data.filmGauge; - if ( data.filmOffset !== undefined ) object.filmOffset = data.filmOffset; - if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); - - break; - - case 'OrthographicCamera': - - object = new OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far ); - - break; - - case 'AmbientLight': - - object = new AmbientLight( data.color, data.intensity ); - - break; - - case 'DirectionalLight': - - object = new DirectionalLight( data.color, data.intensity ); - - break; - - case 'PointLight': - - object = new PointLight( data.color, data.intensity, data.distance, data.decay ); - - break; - - case 'RectAreaLight': - - object = new RectAreaLight( data.color, data.intensity, data.width, data.height ); - - break; - - case 'SpotLight': - - object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay ); - - break; - - case 'HemisphereLight': - - object = new HemisphereLight( data.color, data.groundColor, data.intensity ); - - break; - - case 'SkinnedMesh': - - console.warn( 'THREE.ObjectLoader.parseObject() does not support SkinnedMesh yet.' ); - - case 'Mesh': - - var geometry = getGeometry( data.geometry ); - var material = getMaterial( data.material ); - - if ( geometry.bones && geometry.bones.length > 0 ) { - - object = new SkinnedMesh( geometry, material ); - - } else { - - object = new Mesh( geometry, material ); - - } - - break; - - case 'LOD': - - object = new LOD(); - - break; - - case 'Line': - - object = new Line( getGeometry( data.geometry ), getMaterial( data.material ), data.mode ); - - break; - - case 'LineLoop': - - object = new LineLoop( getGeometry( data.geometry ), getMaterial( data.material ) ); - - break; - - case 'LineSegments': - - object = new LineSegments( getGeometry( data.geometry ), getMaterial( data.material ) ); - - break; - - case 'PointCloud': - case 'Points': - - object = new Points( getGeometry( data.geometry ), getMaterial( data.material ) ); - - break; - - case 'Sprite': - - object = new Sprite( getMaterial( data.material ) ); - - break; - - case 'Group': - - object = new Group(); - - break; - - default: - - object = new Object3D(); - - } - - object.uuid = data.uuid; - - if ( data.name !== undefined ) object.name = data.name; - if ( data.matrix !== undefined ) { - - matrix.fromArray( data.matrix ); - matrix.decompose( object.position, object.quaternion, object.scale ); - - } else { - - if ( data.position !== undefined ) object.position.fromArray( data.position ); - if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation ); - if ( data.quaternion !== undefined ) object.quaternion.fromArray( data.quaternion ); - if ( data.scale !== undefined ) object.scale.fromArray( data.scale ); - - } - - if ( data.castShadow !== undefined ) object.castShadow = data.castShadow; - if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow; - - if ( data.shadow ) { - - if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias; - if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius; - if ( data.shadow.mapSize !== undefined ) object.shadow.mapSize.fromArray( data.shadow.mapSize ); - if ( data.shadow.camera !== undefined ) object.shadow.camera = this.parseObject( data.shadow.camera ); - - } - - if ( data.visible !== undefined ) object.visible = data.visible; - if ( data.userData !== undefined ) object.userData = data.userData; - - if ( data.children !== undefined ) { - - var children = data.children; - - for ( var i = 0; i < children.length; i ++ ) { - - object.add( this.parseObject( children[ i ], geometries, materials ) ); - - } - - } - - if ( data.type === 'LOD' ) { - - var levels = data.levels; - - for ( var l = 0; l < levels.length; l ++ ) { - - var level = levels[ l ]; - var child = object.getObjectByProperty( 'uuid', level.object ); - - if ( child !== undefined ) { - - object.addLevel( child, level.distance ); - - } - - } - - } - - return object; - - }; - - }() - -} ); - -var TEXTURE_MAPPING = { - UVMapping: UVMapping, - CubeReflectionMapping: CubeReflectionMapping, - CubeRefractionMapping: CubeRefractionMapping, - EquirectangularReflectionMapping: EquirectangularReflectionMapping, - EquirectangularRefractionMapping: EquirectangularRefractionMapping, - SphericalReflectionMapping: SphericalReflectionMapping, - CubeUVReflectionMapping: CubeUVReflectionMapping, - CubeUVRefractionMapping: CubeUVRefractionMapping -}; - -var TEXTURE_WRAPPING = { - RepeatWrapping: RepeatWrapping, - ClampToEdgeWrapping: ClampToEdgeWrapping, - MirroredRepeatWrapping: MirroredRepeatWrapping -}; - -var TEXTURE_FILTER = { - NearestFilter: NearestFilter, - NearestMipMapNearestFilter: NearestMipMapNearestFilter, - NearestMipMapLinearFilter: NearestMipMapLinearFilter, - LinearFilter: LinearFilter, - LinearMipMapNearestFilter: LinearMipMapNearestFilter, - LinearMipMapLinearFilter: LinearMipMapLinearFilter -}; - -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - * - * Bezier Curves formulas obtained from - * http://en.wikipedia.org/wiki/Bézier_curve - */ - -function CatmullRom( t, p0, p1, p2, p3 ) { - - var v0 = ( p2 - p0 ) * 0.5; - var v1 = ( p3 - p1 ) * 0.5; - var t2 = t * t; - var t3 = t * t2; - return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; - -} - -// - -function QuadraticBezierP0( t, p ) { - - var k = 1 - t; - return k * k * p; - -} - -function QuadraticBezierP1( t, p ) { - - return 2 * ( 1 - t ) * t * p; - -} - -function QuadraticBezierP2( t, p ) { - - return t * t * p; - -} - -function QuadraticBezier( t, p0, p1, p2 ) { - - return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + - QuadraticBezierP2( t, p2 ); - -} - -// - -function CubicBezierP0( t, p ) { - - var k = 1 - t; - return k * k * k * p; - -} - -function CubicBezierP1( t, p ) { - - var k = 1 - t; - return 3 * k * k * t * p; - -} - -function CubicBezierP2( t, p ) { - - return 3 * ( 1 - t ) * t * t * p; - -} - -function CubicBezierP3( t, p ) { - - return t * t * t * p; - -} - -function CubicBezier( t, p0, p1, p2, p3 ) { - - return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + - CubicBezierP3( t, p3 ); - -} - -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - * Extensible curve object - * - * Some common of curve methods: - * .getPoint( t, optionalTarget ), .getTangent( t ) - * .getPointAt( u, optionalTarget ), .getTangentAt( u ) - * .getPoints(), .getSpacedPoints() - * .getLength() - * .updateArcLengths() - * - * This following curves inherit from THREE.Curve: - * - * -- 2D curves -- - * THREE.ArcCurve - * THREE.CubicBezierCurve - * THREE.EllipseCurve - * THREE.LineCurve - * THREE.QuadraticBezierCurve - * THREE.SplineCurve - * - * -- 3D curves -- - * THREE.CatmullRomCurve3 - * THREE.CubicBezierCurve3 - * THREE.LineCurve3 - * THREE.QuadraticBezierCurve3 - * - * A series of curves can be represented as a THREE.CurvePath. - * - **/ - -/************************************************************** - * Abstract Curve base class - **************************************************************/ - -function Curve() { - - this.type = 'Curve'; - - this.arcLengthDivisions = 200; - -} - -Object.assign( Curve.prototype, { - - // Virtual base class method to overwrite and implement in subclasses - // - t [0 .. 1] - - getPoint: function ( /* t, optionalTarget */ ) { - - console.warn( 'THREE.Curve: .getPoint() not implemented.' ); - return null; - - }, - - // Get point at relative position in curve according to arc length - // - u [0 .. 1] - - getPointAt: function ( u, optionalTarget ) { - - var t = this.getUtoTmapping( u ); - return this.getPoint( t, optionalTarget ); - - }, - - // Get sequence of points using getPoint( t ) - - getPoints: function ( divisions ) { - - if ( divisions === undefined ) divisions = 5; - - var points = []; - - for ( var d = 0; d <= divisions; d ++ ) { - - points.push( this.getPoint( d / divisions ) ); - - } - - return points; - - }, - - // Get sequence of points using getPointAt( u ) - - getSpacedPoints: function ( divisions ) { - - if ( divisions === undefined ) divisions = 5; - - var points = []; - - for ( var d = 0; d <= divisions; d ++ ) { - - points.push( this.getPointAt( d / divisions ) ); - - } - - return points; - - }, - - // Get total curve arc length - - getLength: function () { - - var lengths = this.getLengths(); - return lengths[ lengths.length - 1 ]; - - }, - - // Get list of cumulative segment lengths - - getLengths: function ( divisions ) { - - if ( divisions === undefined ) divisions = this.arcLengthDivisions; - - if ( this.cacheArcLengths && - ( this.cacheArcLengths.length === divisions + 1 ) && - ! this.needsUpdate ) { - - return this.cacheArcLengths; - - } - - this.needsUpdate = false; - - var cache = []; - var current, last = this.getPoint( 0 ); - var p, sum = 0; - - cache.push( 0 ); - - for ( p = 1; p <= divisions; p ++ ) { - - current = this.getPoint( p / divisions ); - sum += current.distanceTo( last ); - cache.push( sum ); - last = current; - - } - - this.cacheArcLengths = cache; - - return cache; // { sums: cache, sum: sum }; Sum is in the last element. - - }, - - updateArcLengths: function () { - - this.needsUpdate = true; - this.getLengths(); - - }, - - // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant - - getUtoTmapping: function ( u, distance ) { - - var arcLengths = this.getLengths(); - - var i = 0, il = arcLengths.length; - - var targetArcLength; // The targeted u distance value to get - - if ( distance ) { - - targetArcLength = distance; - - } else { - - targetArcLength = u * arcLengths[ il - 1 ]; - - } - - // binary search for the index with largest value smaller than target u distance - - var low = 0, high = il - 1, comparison; - - while ( low <= high ) { - - i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats - - comparison = arcLengths[ i ] - targetArcLength; - - if ( comparison < 0 ) { - - low = i + 1; - - } else if ( comparison > 0 ) { - - high = i - 1; - - } else { - - high = i; - break; - - // DONE - - } - - } - - i = high; - - if ( arcLengths[ i ] === targetArcLength ) { - - return i / ( il - 1 ); - - } - - // we could get finer grain at lengths, or use simple interpolation between two points - - var lengthBefore = arcLengths[ i ]; - var lengthAfter = arcLengths[ i + 1 ]; - - var segmentLength = lengthAfter - lengthBefore; - - // determine where we are between the 'before' and 'after' points - - var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; - - // add that fractional amount to t - - var t = ( i + segmentFraction ) / ( il - 1 ); - - return t; - - }, - - // Returns a unit vector tangent at t - // In case any sub curve does not implement its tangent derivation, - // 2 points a small delta apart will be used to find its gradient - // which seems to give a reasonable approximation - - getTangent: function ( t ) { - - var delta = 0.0001; - var t1 = t - delta; - var t2 = t + delta; - - // Capping in case of danger - - if ( t1 < 0 ) t1 = 0; - if ( t2 > 1 ) t2 = 1; - - var pt1 = this.getPoint( t1 ); - var pt2 = this.getPoint( t2 ); - - var vec = pt2.clone().sub( pt1 ); - return vec.normalize(); - - }, - - getTangentAt: function ( u ) { - - var t = this.getUtoTmapping( u ); - return this.getTangent( t ); - - }, - - computeFrenetFrames: function ( segments, closed ) { - - // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf - - var normal = new Vector3(); - - var tangents = []; - var normals = []; - var binormals = []; - - var vec = new Vector3(); - var mat = new Matrix4(); - - var i, u, theta; - - // compute the tangent vectors for each segment on the curve - - for ( i = 0; i <= segments; i ++ ) { - - u = i / segments; - - tangents[ i ] = this.getTangentAt( u ); - tangents[ i ].normalize(); - - } - - // select an initial normal vector perpendicular to the first tangent vector, - // and in the direction of the minimum tangent xyz component - - normals[ 0 ] = new Vector3(); - binormals[ 0 ] = new Vector3(); - var min = Number.MAX_VALUE; - var tx = Math.abs( tangents[ 0 ].x ); - var ty = Math.abs( tangents[ 0 ].y ); - var tz = Math.abs( tangents[ 0 ].z ); - - if ( tx <= min ) { - - min = tx; - normal.set( 1, 0, 0 ); - - } - - if ( ty <= min ) { - - min = ty; - normal.set( 0, 1, 0 ); - - } - - if ( tz <= min ) { - - normal.set( 0, 0, 1 ); - - } - - vec.crossVectors( tangents[ 0 ], normal ).normalize(); - - normals[ 0 ].crossVectors( tangents[ 0 ], vec ); - binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); - - - // compute the slowly-varying normal and binormal vectors for each segment on the curve - - for ( i = 1; i <= segments; i ++ ) { - - normals[ i ] = normals[ i - 1 ].clone(); - - binormals[ i ] = binormals[ i - 1 ].clone(); - - vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); - - if ( vec.length() > Number.EPSILON ) { - - vec.normalize(); - - theta = Math.acos( _Math.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors - - normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); - - } - - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - - } - - // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same - - if ( closed === true ) { - - theta = Math.acos( _Math.clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) ); - theta /= segments; - - if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { - - theta = - theta; - - } - - for ( i = 1; i <= segments; i ++ ) { - - // twist a little... - normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); - binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); - - } - - } - - return { - tangents: tangents, - normals: normals, - binormals: binormals - }; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( source ) { - - this.arcLengthDivisions = source.arcLengthDivisions; - - return this; - - } - -} ); - -function LineCurve( v1, v2 ) { - - Curve.call( this ); - - this.type = 'LineCurve'; - - this.v1 = v1 || new Vector2(); - this.v2 = v2 || new Vector2(); - -} - -LineCurve.prototype = Object.create( Curve.prototype ); -LineCurve.prototype.constructor = LineCurve; - -LineCurve.prototype.isLineCurve = true; - -LineCurve.prototype.getPoint = function ( t, optionalTarget ) { - - var point = optionalTarget || new Vector2(); - - if ( t === 1 ) { - - point.copy( this.v2 ); - - } else { - - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); - - } - - return point; - -}; - -// Line curve is linear, so we can overwrite default getPointAt - -LineCurve.prototype.getPointAt = function ( u, optionalTarget ) { - - return this.getPoint( u, optionalTarget ); - -}; - -LineCurve.prototype.getTangent = function ( /* t */ ) { - - var tangent = this.v2.clone().sub( this.v1 ); - - return tangent.normalize(); - -}; - -LineCurve.prototype.copy = function ( source ) { - - Curve.prototype.copy.call( this, source ); - - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - -}; - -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - * - **/ - -/************************************************************** - * Curved Path - a curve path is simply a array of connected - * curves, but retains the api of a curve - **************************************************************/ - -function CurvePath() { - - Curve.call( this ); - - this.type = 'CurvePath'; - - this.curves = []; - this.autoClose = false; // Automatically closes the path - -} - -CurvePath.prototype = Object.assign( Object.create( Curve.prototype ), { - - constructor: CurvePath, - - add: function ( curve ) { - - this.curves.push( curve ); - - }, - - closePath: function () { - - // Add a line curve if start and end of lines are not connected - var startPoint = this.curves[ 0 ].getPoint( 0 ); - var endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); - - if ( ! startPoint.equals( endPoint ) ) { - - this.curves.push( new LineCurve( endPoint, startPoint ) ); - - } - - }, - - // To get accurate point with reference to - // entire path distance at time t, - // following has to be done: - - // 1. Length of each sub path have to be known - // 2. Locate and identify type of curve - // 3. Get t for the curve - // 4. Return curve.getPointAt(t') - - getPoint: function ( t ) { - - var d = t * this.getLength(); - var curveLengths = this.getCurveLengths(); - var i = 0; - - // To think about boundaries points. - - while ( i < curveLengths.length ) { - - if ( curveLengths[ i ] >= d ) { - - var diff = curveLengths[ i ] - d; - var curve = this.curves[ i ]; - - var segmentLength = curve.getLength(); - var u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; - - return curve.getPointAt( u ); - - } - - i ++; - - } - - return null; - - // loop where sum != 0, sum > d , sum+1 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { - - points.push( points[ 0 ] ); - - } - - return points; - - }, - - copy: function ( source ) { - - Curve.prototype.copy.call( this, source ); - - this.curves = []; - - for ( var i = 0, l = source.curves.length; i < l; i ++ ) { - - var curve = source.curves[ i ]; - - this.curves.push( curve.clone() ); - - } - - this.autoClose = source.autoClose; - - return this; - - } - -} ); - -function EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - - Curve.call( this ); - - this.type = 'EllipseCurve'; - - this.aX = aX || 0; - this.aY = aY || 0; - - this.xRadius = xRadius || 1; - this.yRadius = yRadius || 1; - - this.aStartAngle = aStartAngle || 0; - this.aEndAngle = aEndAngle || 2 * Math.PI; - - this.aClockwise = aClockwise || false; - - this.aRotation = aRotation || 0; - -} - -EllipseCurve.prototype = Object.create( Curve.prototype ); -EllipseCurve.prototype.constructor = EllipseCurve; - -EllipseCurve.prototype.isEllipseCurve = true; - -EllipseCurve.prototype.getPoint = function ( t, optionalTarget ) { - - var point = optionalTarget || new Vector2(); - - var twoPi = Math.PI * 2; - var deltaAngle = this.aEndAngle - this.aStartAngle; - var samePoints = Math.abs( deltaAngle ) < Number.EPSILON; - - // ensures that deltaAngle is 0 .. 2 PI - while ( deltaAngle < 0 ) deltaAngle += twoPi; - while ( deltaAngle > twoPi ) deltaAngle -= twoPi; - - if ( deltaAngle < Number.EPSILON ) { - - if ( samePoints ) { - - deltaAngle = 0; - - } else { - - deltaAngle = twoPi; - - } - - } - - if ( this.aClockwise === true && ! samePoints ) { - - if ( deltaAngle === twoPi ) { - - deltaAngle = - twoPi; - - } else { - - deltaAngle = deltaAngle - twoPi; - - } - - } - - var angle = this.aStartAngle + t * deltaAngle; - var x = this.aX + this.xRadius * Math.cos( angle ); - var y = this.aY + this.yRadius * Math.sin( angle ); - - if ( this.aRotation !== 0 ) { - - var cos = Math.cos( this.aRotation ); - var sin = Math.sin( this.aRotation ); - - var tx = x - this.aX; - var ty = y - this.aY; - - // Rotate the point about the center of the ellipse. - x = tx * cos - ty * sin + this.aX; - y = tx * sin + ty * cos + this.aY; - - } - - return point.set( x, y ); - -}; - -EllipseCurve.prototype.copy = function ( source ) { - - Curve.prototype.copy.call( this, source ); - - this.aX = source.aX; - this.aY = source.aY; - - this.xRadius = source.xRadius; - this.yRadius = source.yRadius; - - this.aStartAngle = source.aStartAngle; - this.aEndAngle = source.aEndAngle; - - this.aClockwise = source.aClockwise; - - this.aRotation = source.aRotation; - - return this; - -}; - -function SplineCurve( points /* array of Vector2 */ ) { - - Curve.call( this ); - - this.type = 'SplineCurve'; - - this.points = points || []; - -} - -SplineCurve.prototype = Object.create( Curve.prototype ); -SplineCurve.prototype.constructor = SplineCurve; - -SplineCurve.prototype.isSplineCurve = true; - -SplineCurve.prototype.getPoint = function ( t, optionalTarget ) { - - var point = optionalTarget || new Vector2(); - - var points = this.points; - var p = ( points.length - 1 ) * t; - - var intPoint = Math.floor( p ); - var weight = p - intPoint; - - var p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; - var p1 = points[ intPoint ]; - var p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; - var p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; - - point.set( - CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), - CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) - ); - - return point; - -}; - -SplineCurve.prototype.copy = function ( source ) { - - Curve.prototype.copy.call( this, source ); - - this.points = []; - - for ( var i = 0, l = source.points.length; i < l; i ++ ) { - - var point = source.points[ i ]; - - this.points.push( point.clone() ); - - } - - return this; - -}; - -function CubicBezierCurve( v0, v1, v2, v3 ) { - - Curve.call( this ); - - this.type = 'CubicBezierCurve'; - - this.v0 = v0 || new Vector2(); - this.v1 = v1 || new Vector2(); - this.v2 = v2 || new Vector2(); - this.v3 = v3 || new Vector2(); - -} - -CubicBezierCurve.prototype = Object.create( Curve.prototype ); -CubicBezierCurve.prototype.constructor = CubicBezierCurve; - -CubicBezierCurve.prototype.isCubicBezierCurve = true; - -CubicBezierCurve.prototype.getPoint = function ( t, optionalTarget ) { - - var point = optionalTarget || new Vector2(); - - var v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) - ); - - return point; - -}; - -CubicBezierCurve.prototype.copy = function ( source ) { - - Curve.prototype.copy.call( this, source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); - - return this; - -}; - -function QuadraticBezierCurve( v0, v1, v2 ) { - - Curve.call( this ); - - this.type = 'QuadraticBezierCurve'; - - this.v0 = v0 || new Vector2(); - this.v1 = v1 || new Vector2(); - this.v2 = v2 || new Vector2(); - -} - -QuadraticBezierCurve.prototype = Object.create( Curve.prototype ); -QuadraticBezierCurve.prototype.constructor = QuadraticBezierCurve; - -QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true; - -QuadraticBezierCurve.prototype.getPoint = function ( t, optionalTarget ) { - - var point = optionalTarget || new Vector2(); - - var v0 = this.v0, v1 = this.v1, v2 = this.v2; - - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ) - ); - - return point; - -}; - -QuadraticBezierCurve.prototype.copy = function ( source ) { - - Curve.prototype.copy.call( this, source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - -}; - -var PathPrototype = Object.assign( Object.create( CurvePath.prototype ), { - - setFromPoints: function ( points ) { - - this.moveTo( points[ 0 ].x, points[ 0 ].y ); - - for ( var i = 1, l = points.length; i < l; i ++ ) { - - this.lineTo( points[ i ].x, points[ i ].y ); - - } - - }, - - moveTo: function ( x, y ) { - - this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? - - }, - - lineTo: function ( x, y ) { - - var curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); - this.curves.push( curve ); - - this.currentPoint.set( x, y ); - - }, - - quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) { - - var curve = new QuadraticBezierCurve( - this.currentPoint.clone(), - new Vector2( aCPx, aCPy ), - new Vector2( aX, aY ) - ); - - this.curves.push( curve ); - - this.currentPoint.set( aX, aY ); - - }, - - bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - - var curve = new CubicBezierCurve( - this.currentPoint.clone(), - new Vector2( aCP1x, aCP1y ), - new Vector2( aCP2x, aCP2y ), - new Vector2( aX, aY ) - ); - - this.curves.push( curve ); - - this.currentPoint.set( aX, aY ); - - }, - - splineThru: function ( pts /*Array of Vector*/ ) { - - var npts = [ this.currentPoint.clone() ].concat( pts ); - - var curve = new SplineCurve( npts ); - this.curves.push( curve ); - - this.currentPoint.copy( pts[ pts.length - 1 ] ); - - }, - - arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - var x0 = this.currentPoint.x; - var y0 = this.currentPoint.y; - - this.absarc( aX + x0, aY + y0, aRadius, - aStartAngle, aEndAngle, aClockwise ); - - }, - - absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - - }, - - ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - - var x0 = this.currentPoint.x; - var y0 = this.currentPoint.y; - - this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - - }, - - absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { - - var curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); - - if ( this.curves.length > 0 ) { - - // if a previous curve is present, attempt to join - var firstPoint = curve.getPoint( 0 ); - - if ( ! firstPoint.equals( this.currentPoint ) ) { - - this.lineTo( firstPoint.x, firstPoint.y ); - - } - - } - - this.curves.push( curve ); - - var lastPoint = curve.getPoint( 1 ); - this.currentPoint.copy( lastPoint ); - - }, - - copy: function ( source ) { - - CurvePath.prototype.copy.call( this, source ); - - this.currentPoint.copy( source.currentPoint ); - - return this; - - } - -} ); - -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - * Creates free form 2d path using series of points, lines or curves. - **/ - -function Path( points ) { - - CurvePath.call( this ); - - this.type = 'Path'; - - this.currentPoint = new Vector2(); - - if ( points ) { - - this.setFromPoints( points ); - - } - -} - -Path.prototype = PathPrototype; -PathPrototype.constructor = Path; - -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - * Defines a 2d shape plane using paths. - **/ - -// STEP 1 Create a path. -// STEP 2 Turn path into shape. -// STEP 3 ExtrudeGeometry takes in Shape/Shapes -// STEP 3a - Extract points from each shape, turn to vertices -// STEP 3b - Triangulate each shape, add faces. - -function Shape( points ) { - - Path.call( this, points ); - - this.type = 'Shape'; - - this.holes = []; - -} - -Shape.prototype = Object.assign( Object.create( PathPrototype ), { - - constructor: Shape, - - getPointsHoles: function ( divisions ) { - - var holesPts = []; - - for ( var i = 0, l = this.holes.length; i < l; i ++ ) { - - holesPts[ i ] = this.holes[ i ].getPoints( divisions ); - - } - - return holesPts; - - }, - - // get points of shape and holes (keypoints based on segments parameter) - - extractPoints: function ( divisions ) { - - return { - - shape: this.getPoints( divisions ), - holes: this.getPointsHoles( divisions ) - - }; - - }, - - copy: function ( source ) { - - Path.prototype.copy.call( this, source ); - - this.holes = []; - - for ( var i = 0, l = source.holes.length; i < l; i ++ ) { - - var hole = source.holes[ i ]; - - this.holes.push( hole.clone() ); - - } - - return this; - - } - -} ); - -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - * minimal class for proxing functions to Path. Replaces old "extractSubpaths()" - **/ - -function ShapePath() { - - this.type = 'ShapePath'; - - this.subPaths = []; - this.currentPath = null; - -} - -Object.assign( ShapePath.prototype, { - - moveTo: function ( x, y ) { - - this.currentPath = new Path(); - this.subPaths.push( this.currentPath ); - this.currentPath.moveTo( x, y ); - - }, - - lineTo: function ( x, y ) { - - this.currentPath.lineTo( x, y ); - - }, - - quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) { - - this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); - - }, - - bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { - - this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); - - }, - - splineThru: function ( pts ) { - - this.currentPath.splineThru( pts ); - - }, - - toShapes: function ( isCCW, noHoles ) { - - function toShapesNoHoles( inSubpaths ) { - - var shapes = []; - - for ( var i = 0, l = inSubpaths.length; i < l; i ++ ) { - - var tmpPath = inSubpaths[ i ]; - - var tmpShape = new Shape(); - tmpShape.curves = tmpPath.curves; - - shapes.push( tmpShape ); - - } - - return shapes; - - } - - function isPointInsidePolygon( inPt, inPolygon ) { - - var polyLen = inPolygon.length; - - // inPt on polygon contour => immediate success or - // toggling of inside/outside at every single! intersection point of an edge - // with the horizontal line through inPt, left of inPt - // not counting lowerY endpoints of edges and whole edges on that line - var inside = false; - for ( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { - - var edgeLowPt = inPolygon[ p ]; - var edgeHighPt = inPolygon[ q ]; - - var edgeDx = edgeHighPt.x - edgeLowPt.x; - var edgeDy = edgeHighPt.y - edgeLowPt.y; - - if ( Math.abs( edgeDy ) > Number.EPSILON ) { - - // not parallel - if ( edgeDy < 0 ) { - - edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; - edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; - - } - if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; - - if ( inPt.y === edgeLowPt.y ) { - - if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? - // continue; // no intersection or edgeLowPt => doesn't count !!! - - } else { - - var perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); - if ( perpEdge === 0 ) return true; // inPt is on contour ? - if ( perpEdge < 0 ) continue; - inside = ! inside; // true intersection left of inPt - - } - - } else { - - // parallel or collinear - if ( inPt.y !== edgeLowPt.y ) continue; // parallel - // edge lies on the same horizontal line as inPt - if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || - ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! - // continue; - - } - - } - - return inside; - - } - - var isClockWise = ShapeUtils.isClockWise; - - var subPaths = this.subPaths; - if ( subPaths.length === 0 ) return []; - - if ( noHoles === true ) return toShapesNoHoles( subPaths ); - - - var solid, tmpPath, tmpShape, shapes = []; - - if ( subPaths.length === 1 ) { - - tmpPath = subPaths[ 0 ]; - tmpShape = new Shape(); - tmpShape.curves = tmpPath.curves; - shapes.push( tmpShape ); - return shapes; - - } - - var holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); - holesFirst = isCCW ? ! holesFirst : holesFirst; - - // console.log("Holes first", holesFirst); - - var betterShapeHoles = []; - var newShapes = []; - var newShapeHoles = []; - var mainIdx = 0; - var tmpPoints; - - newShapes[ mainIdx ] = undefined; - newShapeHoles[ mainIdx ] = []; - - for ( var i = 0, l = subPaths.length; i < l; i ++ ) { - - tmpPath = subPaths[ i ]; - tmpPoints = tmpPath.getPoints(); - solid = isClockWise( tmpPoints ); - solid = isCCW ? ! solid : solid; - - if ( solid ) { - - if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++; - - newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints }; - newShapes[ mainIdx ].s.curves = tmpPath.curves; - - if ( holesFirst ) mainIdx ++; - newShapeHoles[ mainIdx ] = []; - - //console.log('cw', i); - - } else { - - newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); - - //console.log('ccw', i); - - } - - } - - // only Holes? -> probably all Shapes with wrong orientation - if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); - - - if ( newShapes.length > 1 ) { - - var ambiguous = false; - var toChange = []; - - for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { - - betterShapeHoles[ sIdx ] = []; - - } - - for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { - - var sho = newShapeHoles[ sIdx ]; - - for ( var hIdx = 0; hIdx < sho.length; hIdx ++ ) { - - var ho = sho[ hIdx ]; - var hole_unassigned = true; - - for ( var s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { - - if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { - - if ( sIdx !== s2Idx ) toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } ); - if ( hole_unassigned ) { - - hole_unassigned = false; - betterShapeHoles[ s2Idx ].push( ho ); - - } else { - - ambiguous = true; - - } - - } - - } - if ( hole_unassigned ) { - - betterShapeHoles[ sIdx ].push( ho ); - - } - - } - - } - // console.log("ambiguous: ", ambiguous); - if ( toChange.length > 0 ) { - - // console.log("to change: ", toChange); - if ( ! ambiguous ) newShapeHoles = betterShapeHoles; - - } - - } - - var tmpHoles; - - for ( var i = 0, il = newShapes.length; i < il; i ++ ) { - - tmpShape = newShapes[ i ].s; - shapes.push( tmpShape ); - tmpHoles = newShapeHoles[ i ]; - - for ( var j = 0, jl = tmpHoles.length; j < jl; j ++ ) { - - tmpShape.holes.push( tmpHoles[ j ].h ); - - } - - } - - //console.log("shape", shapes); - - return shapes; - - } - -} ); - -/** - * @author zz85 / http://www.lab4games.net/zz85/blog - * @author mrdoob / http://mrdoob.com/ - */ - -function Font( data ) { - - this.type = 'Font'; - - this.data = data; - -} - -Object.assign( Font.prototype, { - - isFont: true, - - generateShapes: function ( text, size, divisions ) { - - function createPaths( text ) { - - var chars = String( text ).split( '' ); - var scale = size / data.resolution; - var line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale; - - var offsetX = 0, offsetY = 0; - - var paths = []; - - for ( var i = 0; i < chars.length; i ++ ) { - - var char = chars[ i ]; - - if ( char === '\n' ) { - - offsetX = 0; - offsetY -= line_height; - - } else { - - var ret = createPath( char, scale, offsetX, offsetY ); - offsetX += ret.offsetX; - paths.push( ret.path ); - - } - - } - - return paths; - - } - - function createPath( c, scale, offsetX, offsetY ) { - - var glyph = data.glyphs[ c ] || data.glyphs[ '?' ]; - - if ( ! glyph ) return; - - var path = new ShapePath(); - - var pts = []; - var x, y, cpx, cpy, cpx0, cpy0, cpx1, cpy1, cpx2, cpy2, laste; - - if ( glyph.o ) { - - var outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); - - for ( var i = 0, l = outline.length; i < l; ) { - - var action = outline[ i ++ ]; - - switch ( action ) { - - case 'm': // moveTo - - x = outline[ i ++ ] * scale + offsetX; - y = outline[ i ++ ] * scale + offsetY; - - path.moveTo( x, y ); - - break; - - case 'l': // lineTo - - x = outline[ i ++ ] * scale + offsetX; - y = outline[ i ++ ] * scale + offsetY; - - path.lineTo( x, y ); - - break; - - case 'q': // quadraticCurveTo - - cpx = outline[ i ++ ] * scale + offsetX; - cpy = outline[ i ++ ] * scale + offsetY; - cpx1 = outline[ i ++ ] * scale + offsetX; - cpy1 = outline[ i ++ ] * scale + offsetY; - - path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); - - laste = pts[ pts.length - 1 ]; - - if ( laste ) { - - cpx0 = laste.x; - cpy0 = laste.y; - - } - - break; - - case 'b': // bezierCurveTo - - cpx = outline[ i ++ ] * scale + offsetX; - cpy = outline[ i ++ ] * scale + offsetY; - cpx1 = outline[ i ++ ] * scale + offsetX; - cpy1 = outline[ i ++ ] * scale + offsetY; - cpx2 = outline[ i ++ ] * scale + offsetX; - cpy2 = outline[ i ++ ] * scale + offsetY; - - path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); - - laste = pts[ pts.length - 1 ]; - - if ( laste ) { - - cpx0 = laste.x; - cpy0 = laste.y; - - } - - break; - - } - - } - - } - - return { offsetX: glyph.ha * scale, path: path }; - - } - - // - - if ( size === undefined ) size = 100; - if ( divisions === undefined ) divisions = 4; - - var data = this.data; - - var paths = createPaths( text ); - var shapes = []; - - for ( var p = 0, pl = paths.length; p < pl; p ++ ) { - - Array.prototype.push.apply( shapes, paths[ p ].toShapes() ); - - } - - return shapes; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function FontLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - -} - -Object.assign( FontLoader.prototype, { - - load: function ( url, onLoad, onProgress, onError ) { - - var scope = this; - - var loader = new FileLoader( this.manager ); - loader.setPath( this.path ); - loader.load( url, function ( text ) { - - var json; - - try { - - json = JSON.parse( text ); - - } catch ( e ) { - - console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' ); - json = JSON.parse( text.substring( 65, text.length - 2 ) ); - - } - - var font = scope.parse( json ); - - if ( onLoad ) onLoad( font ); - - }, onProgress, onError ); - - }, - - parse: function ( json ) { - - return new Font( json ); - - }, - - setPath: function ( value ) { - - this.path = value; - return this; - - } - -} ); - -var context; - -var AudioContext = { - - getContext: function () { - - if ( context === undefined ) { - - context = new ( window.AudioContext || window.webkitAudioContext )(); - - } - - return context; - - }, - - setContext: function ( value ) { - - context = value; - - } - -}; - -/** - * @author Reece Aaron Lecrivain / http://reecenotes.com/ - */ - -function AudioLoader( manager ) { - - this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; - -} - -Object.assign( AudioLoader.prototype, { - - load: function ( url, onLoad, onProgress, onError ) { - - var loader = new FileLoader( this.manager ); - loader.setResponseType( 'arraybuffer' ); - loader.load( url, function ( buffer ) { - - var context = AudioContext.getContext(); - - context.decodeAudioData( buffer, function ( audioBuffer ) { - - onLoad( audioBuffer ); - - } ); - - }, onProgress, onError ); - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function StereoCamera() { - - this.type = 'StereoCamera'; - - this.aspect = 1; - - this.eyeSep = 0.064; - - this.cameraL = new PerspectiveCamera(); - this.cameraL.layers.enable( 1 ); - this.cameraL.matrixAutoUpdate = false; - - this.cameraR = new PerspectiveCamera(); - this.cameraR.layers.enable( 2 ); - this.cameraR.matrixAutoUpdate = false; - -} - -Object.assign( StereoCamera.prototype, { - - update: ( function () { - - var instance, focus, fov, aspect, near, far, zoom, eyeSep; - - var eyeRight = new Matrix4(); - var eyeLeft = new Matrix4(); - - return function update( camera ) { - - var needsUpdate = instance !== this || focus !== camera.focus || fov !== camera.fov || - aspect !== camera.aspect * this.aspect || near !== camera.near || - far !== camera.far || zoom !== camera.zoom || eyeSep !== this.eyeSep; - - if ( needsUpdate ) { - - instance = this; - focus = camera.focus; - fov = camera.fov; - aspect = camera.aspect * this.aspect; - near = camera.near; - far = camera.far; - zoom = camera.zoom; - - // Off-axis stereoscopic effect based on - // http://paulbourke.net/stereographics/stereorender/ - - var projectionMatrix = camera.projectionMatrix.clone(); - eyeSep = this.eyeSep / 2; - var eyeSepOnProjection = eyeSep * near / focus; - var ymax = ( near * Math.tan( _Math.DEG2RAD * fov * 0.5 ) ) / zoom; - var xmin, xmax; - - // translate xOffset - - eyeLeft.elements[ 12 ] = - eyeSep; - eyeRight.elements[ 12 ] = eyeSep; - - // for left eye - - xmin = - ymax * aspect + eyeSepOnProjection; - xmax = ymax * aspect + eyeSepOnProjection; - - projectionMatrix.elements[ 0 ] = 2 * near / ( xmax - xmin ); - projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); - - this.cameraL.projectionMatrix.copy( projectionMatrix ); - - // for right eye - - xmin = - ymax * aspect - eyeSepOnProjection; - xmax = ymax * aspect - eyeSepOnProjection; - - projectionMatrix.elements[ 0 ] = 2 * near / ( xmax - xmin ); - projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); - - this.cameraR.projectionMatrix.copy( projectionMatrix ); - - } - - this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( eyeLeft ); - this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( eyeRight ); - - }; - - } )() - -} ); - -/** - * Camera for rendering cube maps - * - renders scene into axis-aligned cube - * - * @author alteredq / http://alteredqualia.com/ - */ - -function CubeCamera( near, far, cubeResolution ) { - - Object3D.call( this ); - - this.type = 'CubeCamera'; - - var fov = 90, aspect = 1; - - var cameraPX = new PerspectiveCamera( fov, aspect, near, far ); - cameraPX.up.set( 0, - 1, 0 ); - cameraPX.lookAt( new Vector3( 1, 0, 0 ) ); - this.add( cameraPX ); - - var cameraNX = new PerspectiveCamera( fov, aspect, near, far ); - cameraNX.up.set( 0, - 1, 0 ); - cameraNX.lookAt( new Vector3( - 1, 0, 0 ) ); - this.add( cameraNX ); - - var cameraPY = new PerspectiveCamera( fov, aspect, near, far ); - cameraPY.up.set( 0, 0, 1 ); - cameraPY.lookAt( new Vector3( 0, 1, 0 ) ); - this.add( cameraPY ); - - var cameraNY = new PerspectiveCamera( fov, aspect, near, far ); - cameraNY.up.set( 0, 0, - 1 ); - cameraNY.lookAt( new Vector3( 0, - 1, 0 ) ); - this.add( cameraNY ); - - var cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraPZ.up.set( 0, - 1, 0 ); - cameraPZ.lookAt( new Vector3( 0, 0, 1 ) ); - this.add( cameraPZ ); - - var cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); - cameraNZ.up.set( 0, - 1, 0 ); - cameraNZ.lookAt( new Vector3( 0, 0, - 1 ) ); - this.add( cameraNZ ); - - var options = { format: RGBFormat, magFilter: LinearFilter, minFilter: LinearFilter }; - - this.renderTarget = new WebGLRenderTargetCube( cubeResolution, cubeResolution, options ); - this.renderTarget.texture.name = "CubeCamera"; - - this.update = function ( renderer, scene ) { - - if ( this.parent === null ) this.updateMatrixWorld(); - - var renderTarget = this.renderTarget; - var generateMipmaps = renderTarget.texture.generateMipmaps; - - renderTarget.texture.generateMipmaps = false; - - renderTarget.activeCubeFace = 0; - renderer.render( scene, cameraPX, renderTarget ); - - renderTarget.activeCubeFace = 1; - renderer.render( scene, cameraNX, renderTarget ); - - renderTarget.activeCubeFace = 2; - renderer.render( scene, cameraPY, renderTarget ); - - renderTarget.activeCubeFace = 3; - renderer.render( scene, cameraNY, renderTarget ); - - renderTarget.activeCubeFace = 4; - renderer.render( scene, cameraPZ, renderTarget ); - - renderTarget.texture.generateMipmaps = generateMipmaps; - - renderTarget.activeCubeFace = 5; - renderer.render( scene, cameraNZ, renderTarget ); - - renderer.setRenderTarget( null ); - - }; - - this.clear = function ( renderer, color, depth, stencil ) { - - var renderTarget = this.renderTarget; - - for ( var i = 0; i < 6; i ++ ) { - - renderTarget.activeCubeFace = i; - renderer.setRenderTarget( renderTarget ); - - renderer.clear( color, depth, stencil ); - - } - - renderer.setRenderTarget( null ); - - }; - -} - -CubeCamera.prototype = Object.create( Object3D.prototype ); -CubeCamera.prototype.constructor = CubeCamera; - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function AudioListener() { - - Object3D.call( this ); - - this.type = 'AudioListener'; - - this.context = AudioContext.getContext(); - - this.gain = this.context.createGain(); - this.gain.connect( this.context.destination ); - - this.filter = null; - -} - -AudioListener.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: AudioListener, - - getInput: function () { - - return this.gain; - - }, - - removeFilter: function ( ) { - - if ( this.filter !== null ) { - - this.gain.disconnect( this.filter ); - this.filter.disconnect( this.context.destination ); - this.gain.connect( this.context.destination ); - this.filter = null; - - } - - }, - - getFilter: function () { - - return this.filter; - - }, - - setFilter: function ( value ) { - - if ( this.filter !== null ) { - - this.gain.disconnect( this.filter ); - this.filter.disconnect( this.context.destination ); - - } else { - - this.gain.disconnect( this.context.destination ); - - } - - this.filter = value; - this.gain.connect( this.filter ); - this.filter.connect( this.context.destination ); - - }, - - getMasterVolume: function () { - - return this.gain.gain.value; - - }, - - setMasterVolume: function ( value ) { - - this.gain.gain.value = value; - - }, - - updateMatrixWorld: ( function () { - - var position = new Vector3(); - var quaternion = new Quaternion(); - var scale = new Vector3(); - - var orientation = new Vector3(); - - return function updateMatrixWorld( force ) { - - Object3D.prototype.updateMatrixWorld.call( this, force ); - - var listener = this.context.listener; - var up = this.up; - - this.matrixWorld.decompose( position, quaternion, scale ); - - orientation.set( 0, 0, - 1 ).applyQuaternion( quaternion ); - - if ( listener.positionX ) { - - listener.positionX.setValueAtTime( position.x, this.context.currentTime ); - listener.positionY.setValueAtTime( position.y, this.context.currentTime ); - listener.positionZ.setValueAtTime( position.z, this.context.currentTime ); - listener.forwardX.setValueAtTime( orientation.x, this.context.currentTime ); - listener.forwardY.setValueAtTime( orientation.y, this.context.currentTime ); - listener.forwardZ.setValueAtTime( orientation.z, this.context.currentTime ); - listener.upX.setValueAtTime( up.x, this.context.currentTime ); - listener.upY.setValueAtTime( up.y, this.context.currentTime ); - listener.upZ.setValueAtTime( up.z, this.context.currentTime ); - - } else { - - listener.setPosition( position.x, position.y, position.z ); - listener.setOrientation( orientation.x, orientation.y, orientation.z, up.x, up.y, up.z ); - - } - - }; - - } )() - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author Reece Aaron Lecrivain / http://reecenotes.com/ - */ - -function Audio( listener ) { - - Object3D.call( this ); - - this.type = 'Audio'; - - this.context = listener.context; - - this.gain = this.context.createGain(); - this.gain.connect( listener.getInput() ); - - this.autoplay = false; - - this.buffer = null; - this.loop = false; - this.startTime = 0; - this.offset = 0; - this.playbackRate = 1; - this.isPlaying = false; - this.hasPlaybackControl = true; - this.sourceType = 'empty'; - - this.filters = []; - -} - -Audio.prototype = Object.assign( Object.create( Object3D.prototype ), { - - constructor: Audio, - - getOutput: function () { - - return this.gain; - - }, - - setNodeSource: function ( audioNode ) { - - this.hasPlaybackControl = false; - this.sourceType = 'audioNode'; - this.source = audioNode; - this.connect(); - - return this; - - }, - - setBuffer: function ( audioBuffer ) { - - this.buffer = audioBuffer; - this.sourceType = 'buffer'; - - if ( this.autoplay ) this.play(); - - return this; - - }, - - play: function () { - - if ( this.isPlaying === true ) { - - console.warn( 'THREE.Audio: Audio is already playing.' ); - return; - - } - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } - - var source = this.context.createBufferSource(); - - source.buffer = this.buffer; - source.loop = this.loop; - source.onended = this.onEnded.bind( this ); - source.playbackRate.setValueAtTime( this.playbackRate, this.startTime ); - this.startTime = this.context.currentTime; - source.start( this.startTime, this.offset ); - - this.isPlaying = true; - - this.source = source; - - return this.connect(); - - }, - - pause: function () { - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } - - if ( this.isPlaying === true ) { - - this.source.stop(); - this.offset += ( this.context.currentTime - this.startTime ) * this.playbackRate; - this.isPlaying = false; - - } - - return this; - - }, - - stop: function () { - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } - - this.source.stop(); - this.offset = 0; - this.isPlaying = false; - - return this; - - }, - - connect: function () { - - if ( this.filters.length > 0 ) { - - this.source.connect( this.filters[ 0 ] ); - - for ( var i = 1, l = this.filters.length; i < l; i ++ ) { - - this.filters[ i - 1 ].connect( this.filters[ i ] ); - - } - - this.filters[ this.filters.length - 1 ].connect( this.getOutput() ); - - } else { - - this.source.connect( this.getOutput() ); - - } - - return this; - - }, - - disconnect: function () { - - if ( this.filters.length > 0 ) { - - this.source.disconnect( this.filters[ 0 ] ); - - for ( var i = 1, l = this.filters.length; i < l; i ++ ) { - - this.filters[ i - 1 ].disconnect( this.filters[ i ] ); - - } - - this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() ); - - } else { - - this.source.disconnect( this.getOutput() ); - - } - - return this; - - }, - - getFilters: function () { - - return this.filters; - - }, - - setFilters: function ( value ) { - - if ( ! value ) value = []; - - if ( this.isPlaying === true ) { - - this.disconnect(); - this.filters = value; - this.connect(); - - } else { - - this.filters = value; - - } - - return this; - - }, - - getFilter: function () { - - return this.getFilters()[ 0 ]; - - }, - - setFilter: function ( filter ) { - - return this.setFilters( filter ? [ filter ] : [] ); - - }, - - setPlaybackRate: function ( value ) { - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } - - this.playbackRate = value; - - if ( this.isPlaying === true ) { - - this.source.playbackRate.setValueAtTime( this.playbackRate, this.context.currentTime ); - - } - - return this; - - }, - - getPlaybackRate: function () { - - return this.playbackRate; - - }, - - onEnded: function () { - - this.isPlaying = false; - - }, - - getLoop: function () { - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return false; - - } - - return this.loop; - - }, - - setLoop: function ( value ) { - - if ( this.hasPlaybackControl === false ) { - - console.warn( 'THREE.Audio: this Audio has no playback control.' ); - return; - - } - - this.loop = value; - - if ( this.isPlaying === true ) { - - this.source.loop = this.loop; - - } - - return this; - - }, - - getVolume: function () { - - return this.gain.gain.value; - - }, - - setVolume: function ( value ) { - - this.gain.gain.value = value; - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function PositionalAudio( listener ) { - - Audio.call( this, listener ); - - this.panner = this.context.createPanner(); - this.panner.connect( this.gain ); - -} - -PositionalAudio.prototype = Object.assign( Object.create( Audio.prototype ), { - - constructor: PositionalAudio, - - getOutput: function () { - - return this.panner; - - }, - - getRefDistance: function () { - - return this.panner.refDistance; - - }, - - setRefDistance: function ( value ) { - - this.panner.refDistance = value; - - }, - - getRolloffFactor: function () { - - return this.panner.rolloffFactor; - - }, - - setRolloffFactor: function ( value ) { - - this.panner.rolloffFactor = value; - - }, - - getDistanceModel: function () { - - return this.panner.distanceModel; - - }, - - setDistanceModel: function ( value ) { - - this.panner.distanceModel = value; - - }, - - getMaxDistance: function () { - - return this.panner.maxDistance; - - }, - - setMaxDistance: function ( value ) { - - this.panner.maxDistance = value; - - }, - - updateMatrixWorld: ( function () { - - var position = new Vector3(); - - return function updateMatrixWorld( force ) { - - Object3D.prototype.updateMatrixWorld.call( this, force ); - - position.setFromMatrixPosition( this.matrixWorld ); - - this.panner.setPosition( position.x, position.y, position.z ); - - }; - - } )() - - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function AudioAnalyser( audio, fftSize ) { - - this.analyser = audio.context.createAnalyser(); - this.analyser.fftSize = fftSize !== undefined ? fftSize : 2048; - - this.data = new Uint8Array( this.analyser.frequencyBinCount ); - - audio.getOutput().connect( this.analyser ); - -} - -Object.assign( AudioAnalyser.prototype, { - - getFrequencyData: function () { - - this.analyser.getByteFrequencyData( this.data ); - - return this.data; - - }, - - getAverageFrequency: function () { - - var value = 0, data = this.getFrequencyData(); - - for ( var i = 0; i < data.length; i ++ ) { - - value += data[ i ]; - - } - - return value / data.length; - - } - -} ); - -/** - * - * Buffered scene graph property that allows weighted accumulation. - * - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - */ - -function PropertyMixer( binding, typeName, valueSize ) { - - this.binding = binding; - this.valueSize = valueSize; - - var bufferType = Float64Array, - mixFunction; - - switch ( typeName ) { - - case 'quaternion': - mixFunction = this._slerp; - break; - - case 'string': - case 'bool': - bufferType = Array; - mixFunction = this._select; - break; - - default: - mixFunction = this._lerp; - - } - - this.buffer = new bufferType( valueSize * 4 ); - // layout: [ incoming | accu0 | accu1 | orig ] - // - // interpolators can use .buffer as their .result - // the data then goes to 'incoming' - // - // 'accu0' and 'accu1' are used frame-interleaved for - // the cumulative result and are compared to detect - // changes - // - // 'orig' stores the original state of the property - - this._mixBufferRegion = mixFunction; - - this.cumulativeWeight = 0; - - this.useCount = 0; - this.referenceCount = 0; - -} - -Object.assign( PropertyMixer.prototype, { - - // accumulate data in the 'incoming' region into 'accu' - accumulate: function ( accuIndex, weight ) { - - // note: happily accumulating nothing when weight = 0, the caller knows - // the weight and shouldn't have made the call in the first place - - var buffer = this.buffer, - stride = this.valueSize, - offset = accuIndex * stride + stride, - - currentWeight = this.cumulativeWeight; - - if ( currentWeight === 0 ) { - - // accuN := incoming * weight - - for ( var i = 0; i !== stride; ++ i ) { - - buffer[ offset + i ] = buffer[ i ]; - - } - - currentWeight = weight; - - } else { - - // accuN := accuN + incoming * weight - - currentWeight += weight; - var mix = weight / currentWeight; - this._mixBufferRegion( buffer, offset, 0, mix, stride ); - - } - - this.cumulativeWeight = currentWeight; - - }, - - // apply the state of 'accu' to the binding when accus differ - apply: function ( accuIndex ) { - - var stride = this.valueSize, - buffer = this.buffer, - offset = accuIndex * stride + stride, - - weight = this.cumulativeWeight, - - binding = this.binding; - - this.cumulativeWeight = 0; - - if ( weight < 1 ) { - - // accuN := accuN + original * ( 1 - cumulativeWeight ) - - var originalValueOffset = stride * 3; - - this._mixBufferRegion( - buffer, offset, originalValueOffset, 1 - weight, stride ); - - } - - for ( var i = stride, e = stride + stride; i !== e; ++ i ) { - - if ( buffer[ i ] !== buffer[ i + stride ] ) { - - // value has changed -> update scene graph - - binding.setValue( buffer, offset ); - break; - - } - - } - - }, - - // remember the state of the bound property and copy it to both accus - saveOriginalState: function () { - - var binding = this.binding; - - var buffer = this.buffer, - stride = this.valueSize, - - originalValueOffset = stride * 3; - - binding.getValue( buffer, originalValueOffset ); - - // accu[0..1] := orig -- initially detect changes against the original - for ( var i = stride, e = originalValueOffset; i !== e; ++ i ) { - - buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ]; - - } - - this.cumulativeWeight = 0; - - }, - - // apply the state previously taken via 'saveOriginalState' to the binding - restoreOriginalState: function () { - - var originalValueOffset = this.valueSize * 3; - this.binding.setValue( this.buffer, originalValueOffset ); - - }, - - - // mix functions - - _select: function ( buffer, dstOffset, srcOffset, t, stride ) { - - if ( t >= 0.5 ) { - - for ( var i = 0; i !== stride; ++ i ) { - - buffer[ dstOffset + i ] = buffer[ srcOffset + i ]; - - } - - } - - }, - - _slerp: function ( buffer, dstOffset, srcOffset, t ) { - - Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t ); - - }, - - _lerp: function ( buffer, dstOffset, srcOffset, t, stride ) { - - var s = 1 - t; - - for ( var i = 0; i !== stride; ++ i ) { - - var j = dstOffset + i; - - buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t; - - } - - } - -} ); - -/** - * - * A reference to a real property in the scene graph. - * - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - */ - -function Composite( targetGroup, path, optionalParsedPath ) { - - var parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path ); - - this._targetGroup = targetGroup; - this._bindings = targetGroup.subscribe_( path, parsedPath ); - -} - -Object.assign( Composite.prototype, { - - getValue: function ( array, offset ) { - - this.bind(); // bind all binding - - var firstValidIndex = this._targetGroup.nCachedObjects_, - binding = this._bindings[ firstValidIndex ]; - - // and only call .getValue on the first - if ( binding !== undefined ) binding.getValue( array, offset ); - - }, - - setValue: function ( array, offset ) { - - var bindings = this._bindings; - - for ( var i = this._targetGroup.nCachedObjects_, - n = bindings.length; i !== n; ++ i ) { - - bindings[ i ].setValue( array, offset ); - - } - - }, - - bind: function () { - - var bindings = this._bindings; - - for ( var i = this._targetGroup.nCachedObjects_, - n = bindings.length; i !== n; ++ i ) { - - bindings[ i ].bind(); - - } - - }, - - unbind: function () { - - var bindings = this._bindings; - - for ( var i = this._targetGroup.nCachedObjects_, - n = bindings.length; i !== n; ++ i ) { - - bindings[ i ].unbind(); - - } - - } - -} ); - - -function PropertyBinding( rootNode, path, parsedPath ) { - - this.path = path; - this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path ); - - this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode; - - this.rootNode = rootNode; - -} - -Object.assign( PropertyBinding, { - - Composite: Composite, - - create: function ( root, path, parsedPath ) { - - if ( ! ( root && root.isAnimationObjectGroup ) ) { - - return new PropertyBinding( root, path, parsedPath ); - - } else { - - return new PropertyBinding.Composite( root, path, parsedPath ); - - } - - }, - - /** - * Replaces spaces with underscores and removes unsupported characters from - * node names, to ensure compatibility with parseTrackName(). - * - * @param {string} name Node name to be sanitized. - * @return {string} - */ - sanitizeNodeName: function ( name ) { - - return name.replace( /\s/g, '_' ).replace( /[^\w-]/g, '' ); - - }, - - parseTrackName: function () { - - // Parent directories, delimited by '/' or ':'. Currently unused, but must - // be matched to parse the rest of the track name. - var directoryRe = /((?:[\w-]+[\/:])*)/; - - // Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'. - var nodeRe = /([\w-\.]+)?/; - - // Object on target node, and accessor. Name may contain only word - // characters. Accessor may contain any character except closing bracket. - var objectRe = /(?:\.([\w-]+)(?:\[(.+)\])?)?/; - - // Property and accessor. May contain only word characters. Accessor may - // contain any non-bracket characters. - var propertyRe = /\.([\w-]+)(?:\[(.+)\])?/; - - var trackRe = new RegExp( '' - + '^' - + directoryRe.source - + nodeRe.source - + objectRe.source - + propertyRe.source - + '$' - ); - - var supportedObjectNames = [ 'material', 'materials', 'bones' ]; - - return function ( trackName ) { - - var matches = trackRe.exec( trackName ); - - if ( ! matches ) { - - throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName ); - - } - - var results = { - // directoryName: matches[ 1 ], // (tschw) currently unused - nodeName: matches[ 2 ], - objectName: matches[ 3 ], - objectIndex: matches[ 4 ], - propertyName: matches[ 5 ], // required - propertyIndex: matches[ 6 ] - }; - - var lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' ); - - if ( lastDot !== undefined && lastDot !== - 1 ) { - - var objectName = results.nodeName.substring( lastDot + 1 ); - - // Object names must be checked against a whitelist. Otherwise, there - // is no way to parse 'foo.bar.baz': 'baz' must be a property, but - // 'bar' could be the objectName, or part of a nodeName (which can - // include '.' characters). - if ( supportedObjectNames.indexOf( objectName ) !== - 1 ) { - - results.nodeName = results.nodeName.substring( 0, lastDot ); - results.objectName = objectName; - - } - - } - - if ( results.propertyName === null || results.propertyName.length === 0 ) { - - throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName ); - - } - - return results; - - }; - - }(), - - findNode: function ( root, nodeName ) { - - if ( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) { - - return root; - - } - - // search into skeleton bones. - if ( root.skeleton ) { - - var searchSkeleton = function ( skeleton ) { - - for ( var i = 0; i < skeleton.bones.length; i ++ ) { - - var bone = skeleton.bones[ i ]; - - if ( bone.name === nodeName ) { - - return bone; - - } - - } - - return null; - - }; - - var bone = searchSkeleton( root.skeleton ); - - if ( bone ) { - - return bone; - - } - - } - - // search into node subtree. - if ( root.children ) { - - var searchNodeSubtree = function ( children ) { - - for ( var i = 0; i < children.length; i ++ ) { - - var childNode = children[ i ]; - - if ( childNode.name === nodeName || childNode.uuid === nodeName ) { - - return childNode; - - } - - var result = searchNodeSubtree( childNode.children ); - - if ( result ) return result; - - } - - return null; - - }; - - var subTreeNode = searchNodeSubtree( root.children ); - - if ( subTreeNode ) { - - return subTreeNode; - - } - - } - - return null; - - } - -} ); - -Object.assign( PropertyBinding.prototype, { // prototype, continued - - // these are used to "bind" a nonexistent property - _getValue_unavailable: function () {}, - _setValue_unavailable: function () {}, - - BindingType: { - Direct: 0, - EntireArray: 1, - ArrayElement: 2, - HasFromToArray: 3 - }, - - Versioning: { - None: 0, - NeedsUpdate: 1, - MatrixWorldNeedsUpdate: 2 - }, - - GetterByBindingType: [ - - function getValue_direct( buffer, offset ) { - - buffer[ offset ] = this.node[ this.propertyName ]; - - }, - - function getValue_array( buffer, offset ) { - - var source = this.resolvedProperty; - - for ( var i = 0, n = source.length; i !== n; ++ i ) { - - buffer[ offset ++ ] = source[ i ]; - - } - - }, - - function getValue_arrayElement( buffer, offset ) { - - buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ]; - - }, - - function getValue_toArray( buffer, offset ) { - - this.resolvedProperty.toArray( buffer, offset ); - - } - - ], - - SetterByBindingTypeAndVersioning: [ - - [ - // Direct - - function setValue_direct( buffer, offset ) { - - this.targetObject[ this.propertyName ] = buffer[ offset ]; - - }, - - function setValue_direct_setNeedsUpdate( buffer, offset ) { - - this.targetObject[ this.propertyName ] = buffer[ offset ]; - this.targetObject.needsUpdate = true; - - }, - - function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) { - - this.targetObject[ this.propertyName ] = buffer[ offset ]; - this.targetObject.matrixWorldNeedsUpdate = true; - - } - - ], [ - - // EntireArray - - function setValue_array( buffer, offset ) { - - var dest = this.resolvedProperty; - - for ( var i = 0, n = dest.length; i !== n; ++ i ) { - - dest[ i ] = buffer[ offset ++ ]; - - } - - }, - - function setValue_array_setNeedsUpdate( buffer, offset ) { - - var dest = this.resolvedProperty; - - for ( var i = 0, n = dest.length; i !== n; ++ i ) { - - dest[ i ] = buffer[ offset ++ ]; - - } - - this.targetObject.needsUpdate = true; - - }, - - function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) { - - var dest = this.resolvedProperty; - - for ( var i = 0, n = dest.length; i !== n; ++ i ) { - - dest[ i ] = buffer[ offset ++ ]; - - } - - this.targetObject.matrixWorldNeedsUpdate = true; - - } - - ], [ - - // ArrayElement - - function setValue_arrayElement( buffer, offset ) { - - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - - }, - - function setValue_arrayElement_setNeedsUpdate( buffer, offset ) { - - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - this.targetObject.needsUpdate = true; - - }, - - function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) { - - this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; - this.targetObject.matrixWorldNeedsUpdate = true; - - } - - ], [ - - // HasToFromArray - - function setValue_fromArray( buffer, offset ) { - - this.resolvedProperty.fromArray( buffer, offset ); - - }, - - function setValue_fromArray_setNeedsUpdate( buffer, offset ) { - - this.resolvedProperty.fromArray( buffer, offset ); - this.targetObject.needsUpdate = true; - - }, - - function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) { - - this.resolvedProperty.fromArray( buffer, offset ); - this.targetObject.matrixWorldNeedsUpdate = true; - - } - - ] - - ], - - getValue: function getValue_unbound( targetArray, offset ) { - - this.bind(); - this.getValue( targetArray, offset ); - - // Note: This class uses a State pattern on a per-method basis: - // 'bind' sets 'this.getValue' / 'setValue' and shadows the - // prototype version of these methods with one that represents - // the bound state. When the property is not found, the methods - // become no-ops. - - }, - - setValue: function getValue_unbound( sourceArray, offset ) { - - this.bind(); - this.setValue( sourceArray, offset ); - - }, - - // create getter / setter pair for a property in the scene graph - bind: function () { - - var targetObject = this.node, - parsedPath = this.parsedPath, - - objectName = parsedPath.objectName, - propertyName = parsedPath.propertyName, - propertyIndex = parsedPath.propertyIndex; - - if ( ! targetObject ) { - - targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ) || this.rootNode; - - this.node = targetObject; - - } - - // set fail state so we can just 'return' on error - this.getValue = this._getValue_unavailable; - this.setValue = this._setValue_unavailable; - - // ensure there is a value node - if ( ! targetObject ) { - - console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' ); - return; - - } - - if ( objectName ) { - - var objectIndex = parsedPath.objectIndex; - - // special cases were we need to reach deeper into the hierarchy to get the face materials.... - switch ( objectName ) { - - case 'materials': - - if ( ! targetObject.material ) { - - console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); - return; - - } - - if ( ! targetObject.material.materials ) { - - console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this ); - return; - - } - - targetObject = targetObject.material.materials; - - break; - - case 'bones': - - if ( ! targetObject.skeleton ) { - - console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this ); - return; - - } - - // potential future optimization: skip this if propertyIndex is already an integer - // and convert the integer string to a true integer. - - targetObject = targetObject.skeleton.bones; - - // support resolving morphTarget names into indices. - for ( var i = 0; i < targetObject.length; i ++ ) { - - if ( targetObject[ i ].name === objectIndex ) { - - objectIndex = i; - break; - - } - - } - - break; - - default: - - if ( targetObject[ objectName ] === undefined ) { - - console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this ); - return; - - } - - targetObject = targetObject[ objectName ]; - - } - - - if ( objectIndex !== undefined ) { - - if ( targetObject[ objectIndex ] === undefined ) { - - console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject ); - return; - - } - - targetObject = targetObject[ objectIndex ]; - - } - - } - - // resolve property - var nodeProperty = targetObject[ propertyName ]; - - if ( nodeProperty === undefined ) { - - var nodeName = parsedPath.nodeName; - - console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName + - '.' + propertyName + ' but it wasn\'t found.', targetObject ); - return; - - } - - // determine versioning scheme - var versioning = this.Versioning.None; - - if ( targetObject.needsUpdate !== undefined ) { // material - - versioning = this.Versioning.NeedsUpdate; - this.targetObject = targetObject; - - } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform - - versioning = this.Versioning.MatrixWorldNeedsUpdate; - this.targetObject = targetObject; - - } - - // determine how the property gets bound - var bindingType = this.BindingType.Direct; - - if ( propertyIndex !== undefined ) { - - // access a sub element of the property array (only primitives are supported right now) - - if ( propertyName === "morphTargetInfluences" ) { - - // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer. - - // support resolving morphTarget names into indices. - if ( ! targetObject.geometry ) { - - console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this ); - return; - - } - - if ( targetObject.geometry.isBufferGeometry ) { - - if ( ! targetObject.geometry.morphAttributes ) { - - console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this ); - return; - - } - - for ( var i = 0; i < this.node.geometry.morphAttributes.position.length; i ++ ) { - - if ( targetObject.geometry.morphAttributes.position[ i ].name === propertyIndex ) { - - propertyIndex = i; - break; - - } - - } - - - } else { - - if ( ! targetObject.geometry.morphTargets ) { - - console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphTargets.', this ); - return; - - } - - for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) { - - if ( targetObject.geometry.morphTargets[ i ].name === propertyIndex ) { - - propertyIndex = i; - break; - - } - - } - - } - - } - - bindingType = this.BindingType.ArrayElement; - - this.resolvedProperty = nodeProperty; - this.propertyIndex = propertyIndex; - - } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) { - - // must use copy for Object3D.Euler/Quaternion - - bindingType = this.BindingType.HasFromToArray; - - this.resolvedProperty = nodeProperty; - - } else if ( Array.isArray( nodeProperty ) ) { - - bindingType = this.BindingType.EntireArray; - - this.resolvedProperty = nodeProperty; - - } else { - - this.propertyName = propertyName; - - } - - // select getter / setter - this.getValue = this.GetterByBindingType[ bindingType ]; - this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ]; - - }, - - unbind: function () { - - this.node = null; - - // back to the prototype version of getValue / setValue - // note: avoiding to mutate the shape of 'this' via 'delete' - this.getValue = this._getValue_unbound; - this.setValue = this._setValue_unbound; - - } - -} ); - -//!\ DECLARE ALIAS AFTER assign prototype ! -Object.assign( PropertyBinding.prototype, { - - // initial state of these methods that calls 'bind' - _getValue_unbound: PropertyBinding.prototype.getValue, - _setValue_unbound: PropertyBinding.prototype.setValue, - -} ); - -/** - * - * A group of objects that receives a shared animation state. - * - * Usage: - * - * - Add objects you would otherwise pass as 'root' to the - * constructor or the .clipAction method of AnimationMixer. - * - * - Instead pass this object as 'root'. - * - * - You can also add and remove objects later when the mixer - * is running. - * - * Note: - * - * Objects of this class appear as one object to the mixer, - * so cache control of the individual objects must be done - * on the group. - * - * Limitation: - * - * - The animated properties must be compatible among the - * all objects in the group. - * - * - A single property can either be controlled through a - * target group or directly, but not both. - * - * @author tschw - */ - -function AnimationObjectGroup() { - - this.uuid = _Math.generateUUID(); - - // cached objects followed by the active ones - this._objects = Array.prototype.slice.call( arguments ); - - this.nCachedObjects_ = 0; // threshold - // note: read by PropertyBinding.Composite - - var indices = {}; - this._indicesByUUID = indices; // for bookkeeping - - for ( var i = 0, n = arguments.length; i !== n; ++ i ) { - - indices[ arguments[ i ].uuid ] = i; - - } - - this._paths = []; // inside: string - this._parsedPaths = []; // inside: { we don't care, here } - this._bindings = []; // inside: Array< PropertyBinding > - this._bindingsIndicesByPath = {}; // inside: indices in these arrays - - var scope = this; - - this.stats = { - - objects: { - get total() { - - return scope._objects.length; - - }, - get inUse() { - - return this.total - scope.nCachedObjects_; - - } - }, - get bindingsPerObject() { - - return scope._bindings.length; - - } - - }; - -} - -Object.assign( AnimationObjectGroup.prototype, { - - isAnimationObjectGroup: true, - - add: function () { - - var objects = this._objects, - nObjects = objects.length, - nCachedObjects = this.nCachedObjects_, - indicesByUUID = this._indicesByUUID, - paths = this._paths, - parsedPaths = this._parsedPaths, - bindings = this._bindings, - nBindings = bindings.length; - - for ( var i = 0, n = arguments.length; i !== n; ++ i ) { - - var object = arguments[ i ], - uuid = object.uuid, - index = indicesByUUID[ uuid ], - knownObject = undefined; - - if ( index === undefined ) { - - // unknown object -> add it to the ACTIVE region - - index = nObjects ++; - indicesByUUID[ uuid ] = index; - objects.push( object ); - - // accounting is done, now do the same for all bindings - - for ( var j = 0, m = nBindings; j !== m; ++ j ) { - - bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) ); - - } - - } else if ( index < nCachedObjects ) { - - knownObject = objects[ index ]; - - // move existing object to the ACTIVE region - - var firstActiveIndex = -- nCachedObjects, - lastCachedObject = objects[ firstActiveIndex ]; - - indicesByUUID[ lastCachedObject.uuid ] = index; - objects[ index ] = lastCachedObject; - - indicesByUUID[ uuid ] = firstActiveIndex; - objects[ firstActiveIndex ] = object; - - // accounting is done, now do the same for all bindings - - for ( var j = 0, m = nBindings; j !== m; ++ j ) { - - var bindingsForPath = bindings[ j ], - lastCached = bindingsForPath[ firstActiveIndex ], - binding = bindingsForPath[ index ]; - - bindingsForPath[ index ] = lastCached; - - if ( binding === undefined ) { - - // since we do not bother to create new bindings - // for objects that are cached, the binding may - // or may not exist - - binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ); - - } - - bindingsForPath[ firstActiveIndex ] = binding; - - } - - } else if ( objects[ index ] !== knownObject ) { - - console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' + - 'detected. Clean the caches or recreate your infrastructure when reloading scenes.' ); - - } // else the object is already where we want it to be - - } // for arguments - - this.nCachedObjects_ = nCachedObjects; - - }, - - remove: function () { - - var objects = this._objects, - nCachedObjects = this.nCachedObjects_, - indicesByUUID = this._indicesByUUID, - bindings = this._bindings, - nBindings = bindings.length; - - for ( var i = 0, n = arguments.length; i !== n; ++ i ) { - - var object = arguments[ i ], - uuid = object.uuid, - index = indicesByUUID[ uuid ]; - - if ( index !== undefined && index >= nCachedObjects ) { - - // move existing object into the CACHED region - - var lastCachedIndex = nCachedObjects ++, - firstActiveObject = objects[ lastCachedIndex ]; - - indicesByUUID[ firstActiveObject.uuid ] = index; - objects[ index ] = firstActiveObject; - - indicesByUUID[ uuid ] = lastCachedIndex; - objects[ lastCachedIndex ] = object; - - // accounting is done, now do the same for all bindings - - for ( var j = 0, m = nBindings; j !== m; ++ j ) { - - var bindingsForPath = bindings[ j ], - firstActive = bindingsForPath[ lastCachedIndex ], - binding = bindingsForPath[ index ]; - - bindingsForPath[ index ] = firstActive; - bindingsForPath[ lastCachedIndex ] = binding; - - } - - } - - } // for arguments - - this.nCachedObjects_ = nCachedObjects; - - }, - - // remove & forget - uncache: function () { - - var objects = this._objects, - nObjects = objects.length, - nCachedObjects = this.nCachedObjects_, - indicesByUUID = this._indicesByUUID, - bindings = this._bindings, - nBindings = bindings.length; - - for ( var i = 0, n = arguments.length; i !== n; ++ i ) { - - var object = arguments[ i ], - uuid = object.uuid, - index = indicesByUUID[ uuid ]; - - if ( index !== undefined ) { - - delete indicesByUUID[ uuid ]; - - if ( index < nCachedObjects ) { - - // object is cached, shrink the CACHED region - - var firstActiveIndex = -- nCachedObjects, - lastCachedObject = objects[ firstActiveIndex ], - lastIndex = -- nObjects, - lastObject = objects[ lastIndex ]; - - // last cached object takes this object's place - indicesByUUID[ lastCachedObject.uuid ] = index; - objects[ index ] = lastCachedObject; - - // last object goes to the activated slot and pop - indicesByUUID[ lastObject.uuid ] = firstActiveIndex; - objects[ firstActiveIndex ] = lastObject; - objects.pop(); - - // accounting is done, now do the same for all bindings - - for ( var j = 0, m = nBindings; j !== m; ++ j ) { - - var bindingsForPath = bindings[ j ], - lastCached = bindingsForPath[ firstActiveIndex ], - last = bindingsForPath[ lastIndex ]; - - bindingsForPath[ index ] = lastCached; - bindingsForPath[ firstActiveIndex ] = last; - bindingsForPath.pop(); - - } - - } else { - - // object is active, just swap with the last and pop - - var lastIndex = -- nObjects, - lastObject = objects[ lastIndex ]; - - indicesByUUID[ lastObject.uuid ] = index; - objects[ index ] = lastObject; - objects.pop(); - - // accounting is done, now do the same for all bindings - - for ( var j = 0, m = nBindings; j !== m; ++ j ) { - - var bindingsForPath = bindings[ j ]; - - bindingsForPath[ index ] = bindingsForPath[ lastIndex ]; - bindingsForPath.pop(); - - } - - } // cached or active - - } // if object is known - - } // for arguments - - this.nCachedObjects_ = nCachedObjects; - - }, - - // Internal interface used by befriended PropertyBinding.Composite: - - subscribe_: function ( path, parsedPath ) { - - // returns an array of bindings for the given path that is changed - // according to the contained objects in the group - - var indicesByPath = this._bindingsIndicesByPath, - index = indicesByPath[ path ], - bindings = this._bindings; - - if ( index !== undefined ) return bindings[ index ]; - - var paths = this._paths, - parsedPaths = this._parsedPaths, - objects = this._objects, - nObjects = objects.length, - nCachedObjects = this.nCachedObjects_, - bindingsForPath = new Array( nObjects ); - - index = bindings.length; - - indicesByPath[ path ] = index; - - paths.push( path ); - parsedPaths.push( parsedPath ); - bindings.push( bindingsForPath ); - - for ( var i = nCachedObjects, n = objects.length; i !== n; ++ i ) { - - var object = objects[ i ]; - bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath ); - - } - - return bindingsForPath; - - }, - - unsubscribe_: function ( path ) { - - // tells the group to forget about a property path and no longer - // update the array previously obtained with 'subscribe_' - - var indicesByPath = this._bindingsIndicesByPath, - index = indicesByPath[ path ]; - - if ( index !== undefined ) { - - var paths = this._paths, - parsedPaths = this._parsedPaths, - bindings = this._bindings, - lastBindingsIndex = bindings.length - 1, - lastBindings = bindings[ lastBindingsIndex ], - lastBindingsPath = path[ lastBindingsIndex ]; - - indicesByPath[ lastBindingsPath ] = index; - - bindings[ index ] = lastBindings; - bindings.pop(); - - parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ]; - parsedPaths.pop(); - - paths[ index ] = paths[ lastBindingsIndex ]; - paths.pop(); - - } - - } - -} ); - -/** - * - * Action provided by AnimationMixer for scheduling clip playback on specific - * objects. - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - * - */ - -function AnimationAction( mixer, clip, localRoot ) { - - this._mixer = mixer; - this._clip = clip; - this._localRoot = localRoot || null; - - var tracks = clip.tracks, - nTracks = tracks.length, - interpolants = new Array( nTracks ); - - var interpolantSettings = { - endingStart: ZeroCurvatureEnding, - endingEnd: ZeroCurvatureEnding - }; - - for ( var i = 0; i !== nTracks; ++ i ) { - - var interpolant = tracks[ i ].createInterpolant( null ); - interpolants[ i ] = interpolant; - interpolant.settings = interpolantSettings; - - } - - this._interpolantSettings = interpolantSettings; - - this._interpolants = interpolants; // bound by the mixer - - // inside: PropertyMixer (managed by the mixer) - this._propertyBindings = new Array( nTracks ); - - this._cacheIndex = null; // for the memory manager - this._byClipCacheIndex = null; // for the memory manager - - this._timeScaleInterpolant = null; - this._weightInterpolant = null; - - this.loop = LoopRepeat; - this._loopCount = - 1; - - // global mixer time when the action is to be started - // it's set back to 'null' upon start of the action - this._startTime = null; - - // scaled local time of the action - // gets clamped or wrapped to 0..clip.duration according to loop - this.time = 0; - - this.timeScale = 1; - this._effectiveTimeScale = 1; - - this.weight = 1; - this._effectiveWeight = 1; - - this.repetitions = Infinity; // no. of repetitions when looping - - this.paused = false; // true -> zero effective time scale - this.enabled = true; // false -> zero effective weight - - this.clampWhenFinished = false; // keep feeding the last frame? - - this.zeroSlopeAtStart = true; // for smooth interpolation w/o separate - this.zeroSlopeAtEnd = true; // clips for start, loop and end - -} - -Object.assign( AnimationAction.prototype, { - - // State & Scheduling - - play: function () { - - this._mixer._activateAction( this ); - - return this; - - }, - - stop: function () { - - this._mixer._deactivateAction( this ); - - return this.reset(); - - }, - - reset: function () { - - this.paused = false; - this.enabled = true; - - this.time = 0; // restart clip - this._loopCount = - 1; // forget previous loops - this._startTime = null; // forget scheduling - - return this.stopFading().stopWarping(); - - }, - - isRunning: function () { - - return this.enabled && ! this.paused && this.timeScale !== 0 && - this._startTime === null && this._mixer._isActiveAction( this ); - - }, - - // return true when play has been called - isScheduled: function () { - - return this._mixer._isActiveAction( this ); - - }, - - startAt: function ( time ) { - - this._startTime = time; - - return this; - - }, - - setLoop: function ( mode, repetitions ) { - - this.loop = mode; - this.repetitions = repetitions; - - return this; - - }, - - // Weight - - // set the weight stopping any scheduled fading - // although .enabled = false yields an effective weight of zero, this - // method does *not* change .enabled, because it would be confusing - setEffectiveWeight: function ( weight ) { - - this.weight = weight; - - // note: same logic as when updated at runtime - this._effectiveWeight = this.enabled ? weight : 0; - - return this.stopFading(); - - }, - - // return the weight considering fading and .enabled - getEffectiveWeight: function () { - - return this._effectiveWeight; - - }, - - fadeIn: function ( duration ) { - - return this._scheduleFading( duration, 0, 1 ); - - }, - - fadeOut: function ( duration ) { - - return this._scheduleFading( duration, 1, 0 ); - - }, - - crossFadeFrom: function ( fadeOutAction, duration, warp ) { - - fadeOutAction.fadeOut( duration ); - this.fadeIn( duration ); - - if ( warp ) { - - var fadeInDuration = this._clip.duration, - fadeOutDuration = fadeOutAction._clip.duration, - - startEndRatio = fadeOutDuration / fadeInDuration, - endStartRatio = fadeInDuration / fadeOutDuration; - - fadeOutAction.warp( 1.0, startEndRatio, duration ); - this.warp( endStartRatio, 1.0, duration ); - - } - - return this; - - }, - - crossFadeTo: function ( fadeInAction, duration, warp ) { - - return fadeInAction.crossFadeFrom( this, duration, warp ); - - }, - - stopFading: function () { - - var weightInterpolant = this._weightInterpolant; - - if ( weightInterpolant !== null ) { - - this._weightInterpolant = null; - this._mixer._takeBackControlInterpolant( weightInterpolant ); - - } - - return this; - - }, - - // Time Scale Control - - // set the time scale stopping any scheduled warping - // although .paused = true yields an effective time scale of zero, this - // method does *not* change .paused, because it would be confusing - setEffectiveTimeScale: function ( timeScale ) { - - this.timeScale = timeScale; - this._effectiveTimeScale = this.paused ? 0 : timeScale; - - return this.stopWarping(); - - }, - - // return the time scale considering warping and .paused - getEffectiveTimeScale: function () { - - return this._effectiveTimeScale; - - }, - - setDuration: function ( duration ) { - - this.timeScale = this._clip.duration / duration; - - return this.stopWarping(); - - }, - - syncWith: function ( action ) { - - this.time = action.time; - this.timeScale = action.timeScale; - - return this.stopWarping(); - - }, - - halt: function ( duration ) { - - return this.warp( this._effectiveTimeScale, 0, duration ); - - }, - - warp: function ( startTimeScale, endTimeScale, duration ) { - - var mixer = this._mixer, now = mixer.time, - interpolant = this._timeScaleInterpolant, - - timeScale = this.timeScale; - - if ( interpolant === null ) { - - interpolant = mixer._lendControlInterpolant(); - this._timeScaleInterpolant = interpolant; - - } - - var times = interpolant.parameterPositions, - values = interpolant.sampleValues; - - times[ 0 ] = now; - times[ 1 ] = now + duration; - - values[ 0 ] = startTimeScale / timeScale; - values[ 1 ] = endTimeScale / timeScale; - - return this; - - }, - - stopWarping: function () { - - var timeScaleInterpolant = this._timeScaleInterpolant; - - if ( timeScaleInterpolant !== null ) { - - this._timeScaleInterpolant = null; - this._mixer._takeBackControlInterpolant( timeScaleInterpolant ); - - } - - return this; - - }, - - // Object Accessors - - getMixer: function () { - - return this._mixer; - - }, - - getClip: function () { - - return this._clip; - - }, - - getRoot: function () { - - return this._localRoot || this._mixer._root; - - }, - - // Interna - - _update: function ( time, deltaTime, timeDirection, accuIndex ) { - - // called by the mixer - - if ( ! this.enabled ) { - - // call ._updateWeight() to update ._effectiveWeight - - this._updateWeight( time ); - return; - - } - - var startTime = this._startTime; - - if ( startTime !== null ) { - - // check for scheduled start of action - - var timeRunning = ( time - startTime ) * timeDirection; - if ( timeRunning < 0 || timeDirection === 0 ) { - - return; // yet to come / don't decide when delta = 0 - - } - - // start - - this._startTime = null; // unschedule - deltaTime = timeDirection * timeRunning; - - } - - // apply time scale and advance time - - deltaTime *= this._updateTimeScale( time ); - var clipTime = this._updateTime( deltaTime ); - - // note: _updateTime may disable the action resulting in - // an effective weight of 0 - - var weight = this._updateWeight( time ); - - if ( weight > 0 ) { - - var interpolants = this._interpolants; - var propertyMixers = this._propertyBindings; - - for ( var j = 0, m = interpolants.length; j !== m; ++ j ) { - - interpolants[ j ].evaluate( clipTime ); - propertyMixers[ j ].accumulate( accuIndex, weight ); - - } - - } - - }, - - _updateWeight: function ( time ) { - - var weight = 0; - - if ( this.enabled ) { - - weight = this.weight; - var interpolant = this._weightInterpolant; - - if ( interpolant !== null ) { - - var interpolantValue = interpolant.evaluate( time )[ 0 ]; - - weight *= interpolantValue; - - if ( time > interpolant.parameterPositions[ 1 ] ) { - - this.stopFading(); - - if ( interpolantValue === 0 ) { - - // faded out, disable - this.enabled = false; - - } - - } - - } - - } - - this._effectiveWeight = weight; - return weight; - - }, - - _updateTimeScale: function ( time ) { - - var timeScale = 0; - - if ( ! this.paused ) { - - timeScale = this.timeScale; - - var interpolant = this._timeScaleInterpolant; - - if ( interpolant !== null ) { - - var interpolantValue = interpolant.evaluate( time )[ 0 ]; - - timeScale *= interpolantValue; - - if ( time > interpolant.parameterPositions[ 1 ] ) { - - this.stopWarping(); - - if ( timeScale === 0 ) { - - // motion has halted, pause - this.paused = true; - - } else { - - // warp done - apply final time scale - this.timeScale = timeScale; - - } - - } - - } - - } - - this._effectiveTimeScale = timeScale; - return timeScale; - - }, - - _updateTime: function ( deltaTime ) { - - var time = this.time + deltaTime; - - if ( deltaTime === 0 ) return time; - - var duration = this._clip.duration, - - loop = this.loop, - loopCount = this._loopCount; - - if ( loop === LoopOnce ) { - - if ( loopCount === - 1 ) { - - // just started - - this._loopCount = 0; - this._setEndings( true, true, false ); - - } - - handle_stop: { - - if ( time >= duration ) { - - time = duration; - - } else if ( time < 0 ) { - - time = 0; - - } else break handle_stop; - - if ( this.clampWhenFinished ) this.paused = true; - else this.enabled = false; - - this._mixer.dispatchEvent( { - type: 'finished', action: this, - direction: deltaTime < 0 ? - 1 : 1 - } ); - - } - - } else { // repetitive Repeat or PingPong - - var pingPong = ( loop === LoopPingPong ); - - if ( loopCount === - 1 ) { - - // just started - - if ( deltaTime >= 0 ) { - - loopCount = 0; - - this._setEndings( true, this.repetitions === 0, pingPong ); - - } else { - - // when looping in reverse direction, the initial - // transition through zero counts as a repetition, - // so leave loopCount at -1 - - this._setEndings( this.repetitions === 0, true, pingPong ); - - } - - } - - if ( time >= duration || time < 0 ) { - - // wrap around - - var loopDelta = Math.floor( time / duration ); // signed - time -= duration * loopDelta; - - loopCount += Math.abs( loopDelta ); - - var pending = this.repetitions - loopCount; - - if ( pending < 0 ) { - - // have to stop (switch state, clamp time, fire event) - - if ( this.clampWhenFinished ) this.paused = true; - else this.enabled = false; - - time = deltaTime > 0 ? duration : 0; - - this._mixer.dispatchEvent( { - type: 'finished', action: this, - direction: deltaTime > 0 ? 1 : - 1 - } ); - - } else { - - // keep running - - if ( pending === 0 ) { - - // entering the last round - - var atStart = deltaTime < 0; - this._setEndings( atStart, ! atStart, pingPong ); - - } else { - - this._setEndings( false, false, pingPong ); - - } - - this._loopCount = loopCount; - - this._mixer.dispatchEvent( { - type: 'loop', action: this, loopDelta: loopDelta - } ); - - } - - } - - if ( pingPong && ( loopCount & 1 ) === 1 ) { - - // invert time for the "pong round" - - this.time = time; - return duration - time; - - } - - } - - this.time = time; - return time; - - }, - - _setEndings: function ( atStart, atEnd, pingPong ) { - - var settings = this._interpolantSettings; - - if ( pingPong ) { - - settings.endingStart = ZeroSlopeEnding; - settings.endingEnd = ZeroSlopeEnding; - - } else { - - // assuming for LoopOnce atStart == atEnd == true - - if ( atStart ) { - - settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding; - - } else { - - settings.endingStart = WrapAroundEnding; - - } - - if ( atEnd ) { - - settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding; - - } else { - - settings.endingEnd = WrapAroundEnding; - - } - - } - - }, - - _scheduleFading: function ( duration, weightNow, weightThen ) { - - var mixer = this._mixer, now = mixer.time, - interpolant = this._weightInterpolant; - - if ( interpolant === null ) { - - interpolant = mixer._lendControlInterpolant(); - this._weightInterpolant = interpolant; - - } - - var times = interpolant.parameterPositions, - values = interpolant.sampleValues; - - times[ 0 ] = now; values[ 0 ] = weightNow; - times[ 1 ] = now + duration; values[ 1 ] = weightThen; - - return this; - - } - -} ); - -/** - * - * Player for AnimationClips. - * - * - * @author Ben Houston / http://clara.io/ - * @author David Sarno / http://lighthaus.us/ - * @author tschw - */ - -function AnimationMixer( root ) { - - this._root = root; - this._initMemoryManager(); - this._accuIndex = 0; - - this.time = 0; - - this.timeScale = 1.0; - -} - -Object.assign( AnimationMixer.prototype, EventDispatcher.prototype, { - - _bindAction: function ( action, prototypeAction ) { - - var root = action._localRoot || this._root, - tracks = action._clip.tracks, - nTracks = tracks.length, - bindings = action._propertyBindings, - interpolants = action._interpolants, - rootUuid = root.uuid, - bindingsByRoot = this._bindingsByRootAndName, - bindingsByName = bindingsByRoot[ rootUuid ]; - - if ( bindingsByName === undefined ) { - - bindingsByName = {}; - bindingsByRoot[ rootUuid ] = bindingsByName; - - } - - for ( var i = 0; i !== nTracks; ++ i ) { - - var track = tracks[ i ], - trackName = track.name, - binding = bindingsByName[ trackName ]; - - if ( binding !== undefined ) { - - bindings[ i ] = binding; - - } else { - - binding = bindings[ i ]; - - if ( binding !== undefined ) { - - // existing binding, make sure the cache knows - - if ( binding._cacheIndex === null ) { - - ++ binding.referenceCount; - this._addInactiveBinding( binding, rootUuid, trackName ); - - } - - continue; - - } - - var path = prototypeAction && prototypeAction. - _propertyBindings[ i ].binding.parsedPath; - - binding = new PropertyMixer( - PropertyBinding.create( root, trackName, path ), - track.ValueTypeName, track.getValueSize() ); - - ++ binding.referenceCount; - this._addInactiveBinding( binding, rootUuid, trackName ); - - bindings[ i ] = binding; - - } - - interpolants[ i ].resultBuffer = binding.buffer; - - } - - }, - - _activateAction: function ( action ) { - - if ( ! this._isActiveAction( action ) ) { - - if ( action._cacheIndex === null ) { - - // this action has been forgotten by the cache, but the user - // appears to be still using it -> rebind - - var rootUuid = ( action._localRoot || this._root ).uuid, - clipUuid = action._clip.uuid, - actionsForClip = this._actionsByClip[ clipUuid ]; - - this._bindAction( action, - actionsForClip && actionsForClip.knownActions[ 0 ] ); - - this._addInactiveAction( action, clipUuid, rootUuid ); - - } - - var bindings = action._propertyBindings; - - // increment reference counts / sort out state - for ( var i = 0, n = bindings.length; i !== n; ++ i ) { - - var binding = bindings[ i ]; - - if ( binding.useCount ++ === 0 ) { - - this._lendBinding( binding ); - binding.saveOriginalState(); - - } - - } - - this._lendAction( action ); - - } - - }, - - _deactivateAction: function ( action ) { - - if ( this._isActiveAction( action ) ) { - - var bindings = action._propertyBindings; - - // decrement reference counts / sort out state - for ( var i = 0, n = bindings.length; i !== n; ++ i ) { - - var binding = bindings[ i ]; - - if ( -- binding.useCount === 0 ) { - - binding.restoreOriginalState(); - this._takeBackBinding( binding ); - - } - - } - - this._takeBackAction( action ); - - } - - }, - - // Memory manager - - _initMemoryManager: function () { - - this._actions = []; // 'nActiveActions' followed by inactive ones - this._nActiveActions = 0; - - this._actionsByClip = {}; - // inside: - // { - // knownActions: Array< AnimationAction > - used as prototypes - // actionByRoot: AnimationAction - lookup - // } - - - this._bindings = []; // 'nActiveBindings' followed by inactive ones - this._nActiveBindings = 0; - - this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer > - - - this._controlInterpolants = []; // same game as above - this._nActiveControlInterpolants = 0; - - var scope = this; - - this.stats = { - - actions: { - get total() { - - return scope._actions.length; - - }, - get inUse() { - - return scope._nActiveActions; - - } - }, - bindings: { - get total() { - - return scope._bindings.length; - - }, - get inUse() { - - return scope._nActiveBindings; - - } - }, - controlInterpolants: { - get total() { - - return scope._controlInterpolants.length; - - }, - get inUse() { - - return scope._nActiveControlInterpolants; - - } - } - - }; - - }, - - // Memory management for AnimationAction objects - - _isActiveAction: function ( action ) { - - var index = action._cacheIndex; - return index !== null && index < this._nActiveActions; - - }, - - _addInactiveAction: function ( action, clipUuid, rootUuid ) { - - var actions = this._actions, - actionsByClip = this._actionsByClip, - actionsForClip = actionsByClip[ clipUuid ]; - - if ( actionsForClip === undefined ) { - - actionsForClip = { - - knownActions: [ action ], - actionByRoot: {} - - }; - - action._byClipCacheIndex = 0; - - actionsByClip[ clipUuid ] = actionsForClip; - - } else { - - var knownActions = actionsForClip.knownActions; - - action._byClipCacheIndex = knownActions.length; - knownActions.push( action ); - - } - - action._cacheIndex = actions.length; - actions.push( action ); - - actionsForClip.actionByRoot[ rootUuid ] = action; - - }, - - _removeInactiveAction: function ( action ) { - - var actions = this._actions, - lastInactiveAction = actions[ actions.length - 1 ], - cacheIndex = action._cacheIndex; - - lastInactiveAction._cacheIndex = cacheIndex; - actions[ cacheIndex ] = lastInactiveAction; - actions.pop(); - - action._cacheIndex = null; - - - var clipUuid = action._clip.uuid, - actionsByClip = this._actionsByClip, - actionsForClip = actionsByClip[ clipUuid ], - knownActionsForClip = actionsForClip.knownActions, - - lastKnownAction = - knownActionsForClip[ knownActionsForClip.length - 1 ], - - byClipCacheIndex = action._byClipCacheIndex; - - lastKnownAction._byClipCacheIndex = byClipCacheIndex; - knownActionsForClip[ byClipCacheIndex ] = lastKnownAction; - knownActionsForClip.pop(); - - action._byClipCacheIndex = null; - - - var actionByRoot = actionsForClip.actionByRoot, - rootUuid = ( action._localRoot || this._root ).uuid; - - delete actionByRoot[ rootUuid ]; - - if ( knownActionsForClip.length === 0 ) { - - delete actionsByClip[ clipUuid ]; - - } - - this._removeInactiveBindingsForAction( action ); - - }, - - _removeInactiveBindingsForAction: function ( action ) { - - var bindings = action._propertyBindings; - for ( var i = 0, n = bindings.length; i !== n; ++ i ) { - - var binding = bindings[ i ]; - - if ( -- binding.referenceCount === 0 ) { - - this._removeInactiveBinding( binding ); - - } - - } - - }, - - _lendAction: function ( action ) { - - // [ active actions | inactive actions ] - // [ active actions >| inactive actions ] - // s a - // <-swap-> - // a s - - var actions = this._actions, - prevIndex = action._cacheIndex, - - lastActiveIndex = this._nActiveActions ++, - - firstInactiveAction = actions[ lastActiveIndex ]; - - action._cacheIndex = lastActiveIndex; - actions[ lastActiveIndex ] = action; - - firstInactiveAction._cacheIndex = prevIndex; - actions[ prevIndex ] = firstInactiveAction; - - }, - - _takeBackAction: function ( action ) { - - // [ active actions | inactive actions ] - // [ active actions |< inactive actions ] - // a s - // <-swap-> - // s a - - var actions = this._actions, - prevIndex = action._cacheIndex, - - firstInactiveIndex = -- this._nActiveActions, - - lastActiveAction = actions[ firstInactiveIndex ]; - - action._cacheIndex = firstInactiveIndex; - actions[ firstInactiveIndex ] = action; - - lastActiveAction._cacheIndex = prevIndex; - actions[ prevIndex ] = lastActiveAction; - - }, - - // Memory management for PropertyMixer objects - - _addInactiveBinding: function ( binding, rootUuid, trackName ) { - - var bindingsByRoot = this._bindingsByRootAndName, - bindingByName = bindingsByRoot[ rootUuid ], - - bindings = this._bindings; - - if ( bindingByName === undefined ) { - - bindingByName = {}; - bindingsByRoot[ rootUuid ] = bindingByName; - - } - - bindingByName[ trackName ] = binding; - - binding._cacheIndex = bindings.length; - bindings.push( binding ); - - }, - - _removeInactiveBinding: function ( binding ) { - - var bindings = this._bindings, - propBinding = binding.binding, - rootUuid = propBinding.rootNode.uuid, - trackName = propBinding.path, - bindingsByRoot = this._bindingsByRootAndName, - bindingByName = bindingsByRoot[ rootUuid ], - - lastInactiveBinding = bindings[ bindings.length - 1 ], - cacheIndex = binding._cacheIndex; - - lastInactiveBinding._cacheIndex = cacheIndex; - bindings[ cacheIndex ] = lastInactiveBinding; - bindings.pop(); - - delete bindingByName[ trackName ]; - - remove_empty_map: { - - for ( var _ in bindingByName ) break remove_empty_map; // eslint-disable-line no-unused-vars - - delete bindingsByRoot[ rootUuid ]; - - } - - }, - - _lendBinding: function ( binding ) { - - var bindings = this._bindings, - prevIndex = binding._cacheIndex, - - lastActiveIndex = this._nActiveBindings ++, - - firstInactiveBinding = bindings[ lastActiveIndex ]; - - binding._cacheIndex = lastActiveIndex; - bindings[ lastActiveIndex ] = binding; - - firstInactiveBinding._cacheIndex = prevIndex; - bindings[ prevIndex ] = firstInactiveBinding; - - }, - - _takeBackBinding: function ( binding ) { - - var bindings = this._bindings, - prevIndex = binding._cacheIndex, - - firstInactiveIndex = -- this._nActiveBindings, - - lastActiveBinding = bindings[ firstInactiveIndex ]; - - binding._cacheIndex = firstInactiveIndex; - bindings[ firstInactiveIndex ] = binding; - - lastActiveBinding._cacheIndex = prevIndex; - bindings[ prevIndex ] = lastActiveBinding; - - }, - - - // Memory management of Interpolants for weight and time scale - - _lendControlInterpolant: function () { - - var interpolants = this._controlInterpolants, - lastActiveIndex = this._nActiveControlInterpolants ++, - interpolant = interpolants[ lastActiveIndex ]; - - if ( interpolant === undefined ) { - - interpolant = new LinearInterpolant( - new Float32Array( 2 ), new Float32Array( 2 ), - 1, this._controlInterpolantsResultBuffer ); - - interpolant.__cacheIndex = lastActiveIndex; - interpolants[ lastActiveIndex ] = interpolant; - - } - - return interpolant; - - }, - - _takeBackControlInterpolant: function ( interpolant ) { - - var interpolants = this._controlInterpolants, - prevIndex = interpolant.__cacheIndex, - - firstInactiveIndex = -- this._nActiveControlInterpolants, - - lastActiveInterpolant = interpolants[ firstInactiveIndex ]; - - interpolant.__cacheIndex = firstInactiveIndex; - interpolants[ firstInactiveIndex ] = interpolant; - - lastActiveInterpolant.__cacheIndex = prevIndex; - interpolants[ prevIndex ] = lastActiveInterpolant; - - }, - - _controlInterpolantsResultBuffer: new Float32Array( 1 ), - - // return an action for a clip optionally using a custom root target - // object (this method allocates a lot of dynamic memory in case a - // previously unknown clip/root combination is specified) - clipAction: function ( clip, optionalRoot ) { - - var root = optionalRoot || this._root, - rootUuid = root.uuid, - - clipObject = typeof clip === 'string' ? - AnimationClip.findByName( root, clip ) : clip, - - clipUuid = clipObject !== null ? clipObject.uuid : clip, - - actionsForClip = this._actionsByClip[ clipUuid ], - prototypeAction = null; - - if ( actionsForClip !== undefined ) { - - var existingAction = - actionsForClip.actionByRoot[ rootUuid ]; - - if ( existingAction !== undefined ) { - - return existingAction; - - } - - // we know the clip, so we don't have to parse all - // the bindings again but can just copy - prototypeAction = actionsForClip.knownActions[ 0 ]; - - // also, take the clip from the prototype action - if ( clipObject === null ) - clipObject = prototypeAction._clip; - - } - - // clip must be known when specified via string - if ( clipObject === null ) return null; - - // allocate all resources required to run it - var newAction = new AnimationAction( this, clipObject, optionalRoot ); - - this._bindAction( newAction, prototypeAction ); - - // and make the action known to the memory manager - this._addInactiveAction( newAction, clipUuid, rootUuid ); - - return newAction; - - }, - - // get an existing action - existingAction: function ( clip, optionalRoot ) { - - var root = optionalRoot || this._root, - rootUuid = root.uuid, - - clipObject = typeof clip === 'string' ? - AnimationClip.findByName( root, clip ) : clip, - - clipUuid = clipObject ? clipObject.uuid : clip, - - actionsForClip = this._actionsByClip[ clipUuid ]; - - if ( actionsForClip !== undefined ) { - - return actionsForClip.actionByRoot[ rootUuid ] || null; - - } - - return null; - - }, - - // deactivates all previously scheduled actions - stopAllAction: function () { - - var actions = this._actions, - nActions = this._nActiveActions, - bindings = this._bindings, - nBindings = this._nActiveBindings; - - this._nActiveActions = 0; - this._nActiveBindings = 0; - - for ( var i = 0; i !== nActions; ++ i ) { - - actions[ i ].reset(); - - } - - for ( var i = 0; i !== nBindings; ++ i ) { - - bindings[ i ].useCount = 0; - - } - - return this; - - }, - - // advance the time and update apply the animation - update: function ( deltaTime ) { - - deltaTime *= this.timeScale; - - var actions = this._actions, - nActions = this._nActiveActions, - - time = this.time += deltaTime, - timeDirection = Math.sign( deltaTime ), - - accuIndex = this._accuIndex ^= 1; - - // run active actions - - for ( var i = 0; i !== nActions; ++ i ) { - - var action = actions[ i ]; - - action._update( time, deltaTime, timeDirection, accuIndex ); - - } - - // update scene graph - - var bindings = this._bindings, - nBindings = this._nActiveBindings; - - for ( var i = 0; i !== nBindings; ++ i ) { - - bindings[ i ].apply( accuIndex ); - - } - - return this; - - }, - - // return this mixer's root target object - getRoot: function () { - - return this._root; - - }, - - // free all resources specific to a particular clip - uncacheClip: function ( clip ) { - - var actions = this._actions, - clipUuid = clip.uuid, - actionsByClip = this._actionsByClip, - actionsForClip = actionsByClip[ clipUuid ]; - - if ( actionsForClip !== undefined ) { - - // note: just calling _removeInactiveAction would mess up the - // iteration state and also require updating the state we can - // just throw away - - var actionsToRemove = actionsForClip.knownActions; - - for ( var i = 0, n = actionsToRemove.length; i !== n; ++ i ) { - - var action = actionsToRemove[ i ]; - - this._deactivateAction( action ); - - var cacheIndex = action._cacheIndex, - lastInactiveAction = actions[ actions.length - 1 ]; - - action._cacheIndex = null; - action._byClipCacheIndex = null; - - lastInactiveAction._cacheIndex = cacheIndex; - actions[ cacheIndex ] = lastInactiveAction; - actions.pop(); - - this._removeInactiveBindingsForAction( action ); - - } - - delete actionsByClip[ clipUuid ]; - - } - - }, - - // free all resources specific to a particular root target object - uncacheRoot: function ( root ) { - - var rootUuid = root.uuid, - actionsByClip = this._actionsByClip; - - for ( var clipUuid in actionsByClip ) { - - var actionByRoot = actionsByClip[ clipUuid ].actionByRoot, - action = actionByRoot[ rootUuid ]; - - if ( action !== undefined ) { - - this._deactivateAction( action ); - this._removeInactiveAction( action ); - - } - - } - - var bindingsByRoot = this._bindingsByRootAndName, - bindingByName = bindingsByRoot[ rootUuid ]; - - if ( bindingByName !== undefined ) { - - for ( var trackName in bindingByName ) { - - var binding = bindingByName[ trackName ]; - binding.restoreOriginalState(); - this._removeInactiveBinding( binding ); - - } - - } - - }, - - // remove a targeted clip from the cache - uncacheAction: function ( clip, optionalRoot ) { - - var action = this.existingAction( clip, optionalRoot ); - - if ( action !== null ) { - - this._deactivateAction( action ); - this._removeInactiveAction( action ); - - } - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function Uniform( value ) { - - if ( typeof value === 'string' ) { - - console.warn( 'THREE.Uniform: Type parameter is no longer needed.' ); - value = arguments[ 1 ]; - - } - - this.value = value; - -} - -Uniform.prototype.clone = function () { - - return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() ); - -}; - -/** - * @author benaadams / https://twitter.com/ben_a_adams - */ - -function InstancedBufferGeometry() { - - BufferGeometry.call( this ); - - this.type = 'InstancedBufferGeometry'; - this.maxInstancedCount = undefined; - -} - -InstancedBufferGeometry.prototype = Object.assign( Object.create( BufferGeometry.prototype ), { - - constructor: InstancedBufferGeometry, - - isInstancedBufferGeometry: true, - - copy: function ( source ) { - - BufferGeometry.prototype.copy.call( this, source ); - - this.maxInstancedCount = source.maxInstancedCount; - - return this; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - } - -} ); - -/** - * @author benaadams / https://twitter.com/ben_a_adams - */ - -function InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, normalized ) { - - this.uuid = _Math.generateUUID(); - - this.data = interleavedBuffer; - this.itemSize = itemSize; - this.offset = offset; - - this.normalized = normalized === true; - -} - -Object.defineProperties( InterleavedBufferAttribute.prototype, { - - count: { - - get: function () { - - return this.data.count; - - } - - }, - - array: { - - get: function () { - - return this.data.array; - - } - - } - -} ); - -Object.assign( InterleavedBufferAttribute.prototype, { - - isInterleavedBufferAttribute: true, - - setX: function ( index, x ) { - - this.data.array[ index * this.data.stride + this.offset ] = x; - - return this; - - }, - - setY: function ( index, y ) { - - this.data.array[ index * this.data.stride + this.offset + 1 ] = y; - - return this; - - }, - - setZ: function ( index, z ) { - - this.data.array[ index * this.data.stride + this.offset + 2 ] = z; - - return this; - - }, - - setW: function ( index, w ) { - - this.data.array[ index * this.data.stride + this.offset + 3 ] = w; - - return this; - - }, - - getX: function ( index ) { - - return this.data.array[ index * this.data.stride + this.offset ]; - - }, - - getY: function ( index ) { - - return this.data.array[ index * this.data.stride + this.offset + 1 ]; - - }, - - getZ: function ( index ) { - - return this.data.array[ index * this.data.stride + this.offset + 2 ]; - - }, - - getW: function ( index ) { - - return this.data.array[ index * this.data.stride + this.offset + 3 ]; - - }, - - setXY: function ( index, x, y ) { - - index = index * this.data.stride + this.offset; - - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - - return this; - - }, - - setXYZ: function ( index, x, y, z ) { - - index = index * this.data.stride + this.offset; - - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - this.data.array[ index + 2 ] = z; - - return this; - - }, - - setXYZW: function ( index, x, y, z, w ) { - - index = index * this.data.stride + this.offset; - - this.data.array[ index + 0 ] = x; - this.data.array[ index + 1 ] = y; - this.data.array[ index + 2 ] = z; - this.data.array[ index + 3 ] = w; - - return this; - - } - -} ); - -/** - * @author benaadams / https://twitter.com/ben_a_adams - */ - -function InterleavedBuffer( array, stride ) { - - this.uuid = _Math.generateUUID(); - - this.array = array; - this.stride = stride; - this.count = array !== undefined ? array.length / stride : 0; - - this.dynamic = false; - this.updateRange = { offset: 0, count: - 1 }; - - this.onUploadCallback = function () {}; - - this.version = 0; - -} - -Object.defineProperty( InterleavedBuffer.prototype, 'needsUpdate', { - - set: function ( value ) { - - if ( value === true ) this.version ++; - - } - -} ); - -Object.assign( InterleavedBuffer.prototype, { - - isInterleavedBuffer: true, - - setArray: function ( array ) { - - if ( Array.isArray( array ) ) { - - throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); - - } - - this.count = array !== undefined ? array.length / this.stride : 0; - this.array = array; - - }, - - setDynamic: function ( value ) { - - this.dynamic = value; - - return this; - - }, - - copy: function ( source ) { - - this.array = new source.array.constructor( source.array ); - this.count = source.count; - this.stride = source.stride; - this.dynamic = source.dynamic; - - return this; - - }, - - copyAt: function ( index1, attribute, index2 ) { - - index1 *= this.stride; - index2 *= attribute.stride; - - for ( var i = 0, l = this.stride; i < l; i ++ ) { - - this.array[ index1 + i ] = attribute.array[ index2 + i ]; - - } - - return this; - - }, - - set: function ( value, offset ) { - - if ( offset === undefined ) offset = 0; - - this.array.set( value, offset ); - - return this; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - onUpload: function ( callback ) { - - this.onUploadCallback = callback; - - return this; - - } - -} ); - -/** - * @author benaadams / https://twitter.com/ben_a_adams - */ - -function InstancedInterleavedBuffer( array, stride, meshPerAttribute ) { - - InterleavedBuffer.call( this, array, stride ); - - this.meshPerAttribute = meshPerAttribute || 1; - -} - -InstancedInterleavedBuffer.prototype = Object.assign( Object.create( InterleavedBuffer.prototype ), { - - constructor: InstancedInterleavedBuffer, - - isInstancedInterleavedBuffer: true, - - copy: function ( source ) { - - InterleavedBuffer.prototype.copy.call( this, source ); - - this.meshPerAttribute = source.meshPerAttribute; - - return this; - - } - -} ); - -/** - * @author benaadams / https://twitter.com/ben_a_adams - */ - -function InstancedBufferAttribute( array, itemSize, meshPerAttribute ) { - - BufferAttribute.call( this, array, itemSize ); - - this.meshPerAttribute = meshPerAttribute || 1; - -} - -InstancedBufferAttribute.prototype = Object.assign( Object.create( BufferAttribute.prototype ), { - - constructor: InstancedBufferAttribute, - - isInstancedBufferAttribute: true, - - copy: function ( source ) { - - BufferAttribute.prototype.copy.call( this, source ); - - this.meshPerAttribute = source.meshPerAttribute; - - return this; - - } - -} ); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author bhouston / http://clara.io/ - * @author stephomi / http://stephaneginier.com/ - */ - -function Raycaster( origin, direction, near, far ) { - - this.ray = new Ray( origin, direction ); - // direction is assumed to be normalized (for accurate distance calculations) - - this.near = near || 0; - this.far = far || Infinity; - - this.params = { - Mesh: {}, - Line: {}, - LOD: {}, - Points: { threshold: 1 }, - Sprite: {} - }; - - Object.defineProperties( this.params, { - PointCloud: { - get: function () { - - console.warn( 'THREE.Raycaster: params.PointCloud has been renamed to params.Points.' ); - return this.Points; - - } - } - } ); - -} - -function ascSort( a, b ) { - - return a.distance - b.distance; - -} - -function intersectObject( object, raycaster, intersects, recursive ) { - - if ( object.visible === false ) return; - - object.raycast( raycaster, intersects ); - - if ( recursive === true ) { - - var children = object.children; - - for ( var i = 0, l = children.length; i < l; i ++ ) { - - intersectObject( children[ i ], raycaster, intersects, true ); - - } - - } - -} - -Object.assign( Raycaster.prototype, { - - linePrecision: 1, - - set: function ( origin, direction ) { - - // direction is assumed to be normalized (for accurate distance calculations) - - this.ray.set( origin, direction ); - - }, - - setFromCamera: function ( coords, camera ) { - - if ( ( camera && camera.isPerspectiveCamera ) ) { - - this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); - this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); - - } else if ( ( camera && camera.isOrthographicCamera ) ) { - - this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera - this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ); - - } else { - - console.error( 'THREE.Raycaster: Unsupported camera type.' ); - - } - - }, - - intersectObject: function ( object, recursive ) { - - var intersects = []; - - intersectObject( object, this, intersects, recursive ); - - intersects.sort( ascSort ); - - return intersects; - - }, - - intersectObjects: function ( objects, recursive ) { - - var intersects = []; - - if ( Array.isArray( objects ) === false ) { - - console.warn( 'THREE.Raycaster.intersectObjects: objects is not an Array.' ); - return intersects; - - } - - for ( var i = 0, l = objects.length; i < l; i ++ ) { - - intersectObject( objects[ i ], this, intersects, recursive ); - - } - - intersects.sort( ascSort ); - - return intersects; - - } - -} ); - -/** - * @author alteredq / http://alteredqualia.com/ - */ - -function Clock( autoStart ) { - - this.autoStart = ( autoStart !== undefined ) ? autoStart : true; - - this.startTime = 0; - this.oldTime = 0; - this.elapsedTime = 0; - - this.running = false; - -} - -Object.assign( Clock.prototype, { - - start: function () { - - this.startTime = ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732 - - this.oldTime = this.startTime; - this.elapsedTime = 0; - this.running = true; - - }, - - stop: function () { - - this.getElapsedTime(); - this.running = false; - this.autoStart = false; - - }, - - getElapsedTime: function () { - - this.getDelta(); - return this.elapsedTime; - - }, - - getDelta: function () { - - var diff = 0; - - if ( this.autoStart && ! this.running ) { - - this.start(); - return 0; - - } - - if ( this.running ) { - - var newTime = ( typeof performance === 'undefined' ? Date : performance ).now(); - - diff = ( newTime - this.oldTime ) / 1000; - this.oldTime = newTime; - - this.elapsedTime += diff; - - } - - return diff; - - } - -} ); - -/** - * @author bhouston / http://clara.io - * @author WestLangley / http://github.com/WestLangley - * - * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system - * - * The poles (phi) are at the positive and negative y axis. - * The equator starts at positive z. - */ - -function Spherical( radius, phi, theta ) { - - this.radius = ( radius !== undefined ) ? radius : 1.0; - this.phi = ( phi !== undefined ) ? phi : 0; // up / down towards top and bottom pole - this.theta = ( theta !== undefined ) ? theta : 0; // around the equator of the sphere - - return this; - -} - -Object.assign( Spherical.prototype, { - - set: function ( radius, phi, theta ) { - - this.radius = radius; - this.phi = phi; - this.theta = theta; - - return this; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( other ) { - - this.radius = other.radius; - this.phi = other.phi; - this.theta = other.theta; - - return this; - - }, - - // restrict phi to be betwee EPS and PI-EPS - makeSafe: function () { - - var EPS = 0.000001; - this.phi = Math.max( EPS, Math.min( Math.PI - EPS, this.phi ) ); - - return this; - - }, - - setFromVector3: function ( vec3 ) { - - this.radius = vec3.length(); - - if ( this.radius === 0 ) { - - this.theta = 0; - this.phi = 0; - - } else { - - this.theta = Math.atan2( vec3.x, vec3.z ); // equator angle around y-up axis - this.phi = Math.acos( _Math.clamp( vec3.y / this.radius, - 1, 1 ) ); // polar angle - - } - - return this; - - } - -} ); - -/** - * @author Mugen87 / https://github.com/Mugen87 - * - * Ref: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system - * - */ - -function Cylindrical( radius, theta, y ) { - - this.radius = ( radius !== undefined ) ? radius : 1.0; // distance from the origin to a point in the x-z plane - this.theta = ( theta !== undefined ) ? theta : 0; // counterclockwise angle in the x-z plane measured in radians from the positive z-axis - this.y = ( y !== undefined ) ? y : 0; // height above the x-z plane - - return this; - -} - -Object.assign( Cylindrical.prototype, { - - set: function ( radius, theta, y ) { - - this.radius = radius; - this.theta = theta; - this.y = y; - - return this; - - }, - - clone: function () { - - return new this.constructor().copy( this ); - - }, - - copy: function ( other ) { - - this.radius = other.radius; - this.theta = other.theta; - this.y = other.y; - - return this; - - }, - - setFromVector3: function ( vec3 ) { - - this.radius = Math.sqrt( vec3.x * vec3.x + vec3.z * vec3.z ); - this.theta = Math.atan2( vec3.x, vec3.z ); - this.y = vec3.y; - - return this; - - } - -} ); - -/** - * @author alteredq / http://alteredqualia.com/ - */ - -function ImmediateRenderObject( material ) { - - Object3D.call( this ); - - this.material = material; - this.render = function ( /* renderCallback */ ) {}; - -} - -ImmediateRenderObject.prototype = Object.create( Object3D.prototype ); -ImmediateRenderObject.prototype.constructor = ImmediateRenderObject; - -ImmediateRenderObject.prototype.isImmediateRenderObject = true; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author WestLangley / http://github.com/WestLangley - */ - -function VertexNormalsHelper( object, size, hex, linewidth ) { - - this.object = object; - - this.size = ( size !== undefined ) ? size : 1; - - var color = ( hex !== undefined ) ? hex : 0xff0000; - - var width = ( linewidth !== undefined ) ? linewidth : 1; - - // - - var nNormals = 0; - - var objGeometry = this.object.geometry; - - if ( objGeometry && objGeometry.isGeometry ) { - - nNormals = objGeometry.faces.length * 3; - - } else if ( objGeometry && objGeometry.isBufferGeometry ) { - - nNormals = objGeometry.attributes.normal.count; - - } - - // - - var geometry = new BufferGeometry(); - - var positions = new Float32BufferAttribute( nNormals * 2 * 3, 3 ); - - geometry.addAttribute( 'position', positions ); - - LineSegments.call( this, geometry, new LineBasicMaterial( { color: color, linewidth: width } ) ); - - // - - this.matrixAutoUpdate = false; - - this.update(); - -} - -VertexNormalsHelper.prototype = Object.create( LineSegments.prototype ); -VertexNormalsHelper.prototype.constructor = VertexNormalsHelper; - -VertexNormalsHelper.prototype.update = ( function () { - - var v1 = new Vector3(); - var v2 = new Vector3(); - var normalMatrix = new Matrix3(); - - return function update() { - - var keys = [ 'a', 'b', 'c' ]; - - this.object.updateMatrixWorld( true ); - - normalMatrix.getNormalMatrix( this.object.matrixWorld ); - - var matrixWorld = this.object.matrixWorld; - - var position = this.geometry.attributes.position; - - // - - var objGeometry = this.object.geometry; - - if ( objGeometry && objGeometry.isGeometry ) { - - var vertices = objGeometry.vertices; - - var faces = objGeometry.faces; - - var idx = 0; - - for ( var i = 0, l = faces.length; i < l; i ++ ) { - - var face = faces[ i ]; - - for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { - - var vertex = vertices[ face[ keys[ j ] ] ]; - - var normal = face.vertexNormals[ j ]; - - v1.copy( vertex ).applyMatrix4( matrixWorld ); - - v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 ); - - position.setXYZ( idx, v1.x, v1.y, v1.z ); - - idx = idx + 1; - - position.setXYZ( idx, v2.x, v2.y, v2.z ); - - idx = idx + 1; - - } - - } - - } else if ( objGeometry && objGeometry.isBufferGeometry ) { - - var objPos = objGeometry.attributes.position; - - var objNorm = objGeometry.attributes.normal; - - var idx = 0; - - // for simplicity, ignore index and drawcalls, and render every normal - - for ( var j = 0, jl = objPos.count; j < jl; j ++ ) { - - v1.set( objPos.getX( j ), objPos.getY( j ), objPos.getZ( j ) ).applyMatrix4( matrixWorld ); - - v2.set( objNorm.getX( j ), objNorm.getY( j ), objNorm.getZ( j ) ); - - v2.applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 ); - - position.setXYZ( idx, v1.x, v1.y, v1.z ); - - idx = idx + 1; - - position.setXYZ( idx, v2.x, v2.y, v2.z ); - - idx = idx + 1; - - } - - } - - position.needsUpdate = true; - - }; - -}() ); - -/** - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - * @author WestLangley / http://github.com/WestLangley - */ - -function SpotLightHelper( light, color ) { - - Object3D.call( this ); - - this.light = light; - this.light.updateMatrixWorld(); - - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; - - this.color = color; - - var geometry = new BufferGeometry(); - - var positions = [ - 0, 0, 0, 0, 0, 1, - 0, 0, 0, 1, 0, 1, - 0, 0, 0, - 1, 0, 1, - 0, 0, 0, 0, 1, 1, - 0, 0, 0, 0, - 1, 1 - ]; - - for ( var i = 0, j = 1, l = 32; i < l; i ++, j ++ ) { - - var p1 = ( i / l ) * Math.PI * 2; - var p2 = ( j / l ) * Math.PI * 2; - - positions.push( - Math.cos( p1 ), Math.sin( p1 ), 1, - Math.cos( p2 ), Math.sin( p2 ), 1 - ); - - } - - geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - - var material = new LineBasicMaterial( { fog: false } ); - - this.cone = new LineSegments( geometry, material ); - this.add( this.cone ); - - this.update(); - -} - -SpotLightHelper.prototype = Object.create( Object3D.prototype ); -SpotLightHelper.prototype.constructor = SpotLightHelper; - -SpotLightHelper.prototype.dispose = function () { - - this.cone.geometry.dispose(); - this.cone.material.dispose(); - -}; - -SpotLightHelper.prototype.update = function () { - - var vector = new Vector3(); - var vector2 = new Vector3(); - - return function update() { - - this.light.updateMatrixWorld(); - - var coneLength = this.light.distance ? this.light.distance : 1000; - var coneWidth = coneLength * Math.tan( this.light.angle ); - - this.cone.scale.set( coneWidth, coneWidth, coneLength ); - - vector.setFromMatrixPosition( this.light.matrixWorld ); - vector2.setFromMatrixPosition( this.light.target.matrixWorld ); - - this.cone.lookAt( vector2.sub( vector ) ); - - if ( this.color !== undefined ) { - - this.cone.material.color.set( this.color ); - - } else { - - this.cone.material.color.copy( this.light.color ); - - } - - }; - -}(); - -/** - * @author Sean Griffin / http://twitter.com/sgrif - * @author Michael Guerrero / http://realitymeltdown.com - * @author mrdoob / http://mrdoob.com/ - * @author ikerr / http://verold.com - * @author Mugen87 / https://github.com/Mugen87 - */ - -function getBoneList( object ) { - - var boneList = []; - - if ( object && object.isBone ) { - - boneList.push( object ); - - } - - for ( var i = 0; i < object.children.length; i ++ ) { - - boneList.push.apply( boneList, getBoneList( object.children[ i ] ) ); - - } - - return boneList; - -} - -function SkeletonHelper( object ) { - - var bones = getBoneList( object ); - - var geometry = new BufferGeometry(); - - var vertices = []; - var colors = []; - - var color1 = new Color( 0, 0, 1 ); - var color2 = new Color( 0, 1, 0 ); - - for ( var i = 0; i < bones.length; i ++ ) { - - var bone = bones[ i ]; - - if ( bone.parent && bone.parent.isBone ) { - - vertices.push( 0, 0, 0 ); - vertices.push( 0, 0, 0 ); - colors.push( color1.r, color1.g, color1.b ); - colors.push( color2.r, color2.g, color2.b ); - - } - - } - - geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - var material = new LineBasicMaterial( { vertexColors: VertexColors, depthTest: false, depthWrite: false, transparent: true } ); - - LineSegments.call( this, geometry, material ); - - this.root = object; - this.bones = bones; - - this.matrix = object.matrixWorld; - this.matrixAutoUpdate = false; - -} - -SkeletonHelper.prototype = Object.create( LineSegments.prototype ); -SkeletonHelper.prototype.constructor = SkeletonHelper; - -SkeletonHelper.prototype.updateMatrixWorld = function () { - - var vector = new Vector3(); - - var boneMatrix = new Matrix4(); - var matrixWorldInv = new Matrix4(); - - return function updateMatrixWorld( force ) { - - var bones = this.bones; - - var geometry = this.geometry; - var position = geometry.getAttribute( 'position' ); - - matrixWorldInv.getInverse( this.root.matrixWorld ); - - for ( var i = 0, j = 0; i < bones.length; i ++ ) { - - var bone = bones[ i ]; - - if ( bone.parent && bone.parent.isBone ) { - - boneMatrix.multiplyMatrices( matrixWorldInv, bone.matrixWorld ); - vector.setFromMatrixPosition( boneMatrix ); - position.setXYZ( j, vector.x, vector.y, vector.z ); - - boneMatrix.multiplyMatrices( matrixWorldInv, bone.parent.matrixWorld ); - vector.setFromMatrixPosition( boneMatrix ); - position.setXYZ( j + 1, vector.x, vector.y, vector.z ); - - j += 2; - - } - - } - - geometry.getAttribute( 'position' ).needsUpdate = true; - - Object3D.prototype.updateMatrixWorld.call( this, force ); - - }; - -}(); - -/** - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - */ - -function PointLightHelper( light, sphereSize, color ) { - - this.light = light; - this.light.updateMatrixWorld(); - - this.color = color; - - var geometry = new SphereBufferGeometry( sphereSize, 4, 2 ); - var material = new MeshBasicMaterial( { wireframe: true, fog: false } ); - - Mesh.call( this, geometry, material ); - - this.matrix = this.light.matrixWorld; - this.matrixAutoUpdate = false; - - this.update(); - - - /* - var distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 ); - var distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } ); - - this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); - this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); - - var d = light.distance; - - if ( d === 0.0 ) { - - this.lightDistance.visible = false; - - } else { - - this.lightDistance.scale.set( d, d, d ); - - } - - this.add( this.lightDistance ); - */ - -} - -PointLightHelper.prototype = Object.create( Mesh.prototype ); -PointLightHelper.prototype.constructor = PointLightHelper; - -PointLightHelper.prototype.dispose = function () { - - this.geometry.dispose(); - this.material.dispose(); - -}; - -PointLightHelper.prototype.update = function () { - - if ( this.color !== undefined ) { - - this.material.color.set( this.color ); - - } else { - - this.material.color.copy( this.light.color ); - - } - - /* - var d = this.light.distance; - - if ( d === 0.0 ) { - - this.lightDistance.visible = false; - - } else { - - this.lightDistance.visible = true; - this.lightDistance.scale.set( d, d, d ); - - } - */ - -}; - -/** - * @author abelnation / http://github.com/abelnation - * @author Mugen87 / http://github.com/Mugen87 - * @author WestLangley / http://github.com/WestLangley - */ - -function RectAreaLightHelper( light, color ) { - - Object3D.call( this ); - - this.light = light; - this.light.updateMatrixWorld(); - - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; - - this.color = color; - - var material = new LineBasicMaterial( { fog: false } ); - - var geometry = new BufferGeometry(); - - geometry.addAttribute( 'position', new BufferAttribute( new Float32Array( 5 * 3 ), 3 ) ); - - this.line = new Line( geometry, material ); - this.add( this.line ); - - - this.update(); - -} - -RectAreaLightHelper.prototype = Object.create( Object3D.prototype ); -RectAreaLightHelper.prototype.constructor = RectAreaLightHelper; - -RectAreaLightHelper.prototype.dispose = function () { - - this.children[ 0 ].geometry.dispose(); - this.children[ 0 ].material.dispose(); - -}; - -RectAreaLightHelper.prototype.update = function () { - - // calculate new dimensions of the helper - - var hx = this.light.width * 0.5; - var hy = this.light.height * 0.5; - - var position = this.line.geometry.attributes.position; - var array = position.array; - - // update vertices - - array[ 0 ] = hx; array[ 1 ] = - hy; array[ 2 ] = 0; - array[ 3 ] = hx; array[ 4 ] = hy; array[ 5 ] = 0; - array[ 6 ] = - hx; array[ 7 ] = hy; array[ 8 ] = 0; - array[ 9 ] = - hx; array[ 10 ] = - hy; array[ 11 ] = 0; - array[ 12 ] = hx; array[ 13 ] = - hy; array[ 14 ] = 0; - - position.needsUpdate = true; - - if ( this.color !== undefined ) { - - this.line.material.color.set( this.color ); - - } else { - - this.line.material.color.copy( this.light.color ); - - } - -}; - -/** - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - * @author Mugen87 / https://github.com/Mugen87 - */ - -function HemisphereLightHelper( light, size, color ) { - - Object3D.call( this ); - - this.light = light; - this.light.updateMatrixWorld(); - - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; - - this.color = color; - - var geometry = new OctahedronBufferGeometry( size ); - geometry.rotateY( Math.PI * 0.5 ); - - this.material = new MeshBasicMaterial( { wireframe: true, fog: false } ); - if ( this.color === undefined ) this.material.vertexColors = VertexColors; - - var position = geometry.getAttribute( 'position' ); - var colors = new Float32Array( position.count * 3 ); - - geometry.addAttribute( 'color', new BufferAttribute( colors, 3 ) ); - - this.add( new Mesh( geometry, this.material ) ); - - this.update(); - -} - -HemisphereLightHelper.prototype = Object.create( Object3D.prototype ); -HemisphereLightHelper.prototype.constructor = HemisphereLightHelper; - -HemisphereLightHelper.prototype.dispose = function () { - - this.children[ 0 ].geometry.dispose(); - this.children[ 0 ].material.dispose(); - -}; - -HemisphereLightHelper.prototype.update = function () { - - var vector = new Vector3(); - - var color1 = new Color(); - var color2 = new Color(); - - return function update() { - - var mesh = this.children[ 0 ]; - - if ( this.color !== undefined ) { - - this.material.color.set( this.color ); - - } else { - - var colors = mesh.geometry.getAttribute( 'color' ); - - color1.copy( this.light.color ); - color2.copy( this.light.groundColor ); - - for ( var i = 0, l = colors.count; i < l; i ++ ) { - - var color = ( i < ( l / 2 ) ) ? color1 : color2; - - colors.setXYZ( i, color.r, color.g, color.b ); - - } - - colors.needsUpdate = true; - - } - - mesh.lookAt( vector.setFromMatrixPosition( this.light.matrixWorld ).negate() ); - - }; - -}(); - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function GridHelper( size, divisions, color1, color2 ) { - - size = size || 10; - divisions = divisions || 10; - color1 = new Color( color1 !== undefined ? color1 : 0x444444 ); - color2 = new Color( color2 !== undefined ? color2 : 0x888888 ); - - var center = divisions / 2; - var step = size / divisions; - var halfSize = size / 2; - - var vertices = [], colors = []; - - for ( var i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) { - - vertices.push( - halfSize, 0, k, halfSize, 0, k ); - vertices.push( k, 0, - halfSize, k, 0, halfSize ); - - var color = i === center ? color1 : color2; - - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; - color.toArray( colors, j ); j += 3; - - } - - var geometry = new BufferGeometry(); - geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - var material = new LineBasicMaterial( { vertexColors: VertexColors } ); - - LineSegments.call( this, geometry, material ); - -} - -GridHelper.prototype = Object.create( LineSegments.prototype ); -GridHelper.prototype.constructor = GridHelper; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author Mugen87 / http://github.com/Mugen87 - * @author Hectate / http://www.github.com/Hectate - */ - -function PolarGridHelper( radius, radials, circles, divisions, color1, color2 ) { - - radius = radius || 10; - radials = radials || 16; - circles = circles || 8; - divisions = divisions || 64; - color1 = new Color( color1 !== undefined ? color1 : 0x444444 ); - color2 = new Color( color2 !== undefined ? color2 : 0x888888 ); - - var vertices = []; - var colors = []; - - var x, z; - var v, i, j, r, color; - - // create the radials - - for ( i = 0; i <= radials; i ++ ) { - - v = ( i / radials ) * ( Math.PI * 2 ); - - x = Math.sin( v ) * radius; - z = Math.cos( v ) * radius; - - vertices.push( 0, 0, 0 ); - vertices.push( x, 0, z ); - - color = ( i & 1 ) ? color1 : color2; - - colors.push( color.r, color.g, color.b ); - colors.push( color.r, color.g, color.b ); - - } - - // create the circles - - for ( i = 0; i <= circles; i ++ ) { - - color = ( i & 1 ) ? color1 : color2; - - r = radius - ( radius / circles * i ); - - for ( j = 0; j < divisions; j ++ ) { - - // first vertex - - v = ( j / divisions ) * ( Math.PI * 2 ); - - x = Math.sin( v ) * r; - z = Math.cos( v ) * r; - - vertices.push( x, 0, z ); - colors.push( color.r, color.g, color.b ); - - // second vertex - - v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 ); - - x = Math.sin( v ) * r; - z = Math.cos( v ) * r; - - vertices.push( x, 0, z ); - colors.push( color.r, color.g, color.b ); - - } - - } - - var geometry = new BufferGeometry(); - geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - var material = new LineBasicMaterial( { vertexColors: VertexColors } ); - - LineSegments.call( this, geometry, material ); - -} - -PolarGridHelper.prototype = Object.create( LineSegments.prototype ); -PolarGridHelper.prototype.constructor = PolarGridHelper; - -/** - * @author mrdoob / http://mrdoob.com/ - * @author WestLangley / http://github.com/WestLangley - */ - -function FaceNormalsHelper( object, size, hex, linewidth ) { - - // FaceNormalsHelper only supports THREE.Geometry - - this.object = object; - - this.size = ( size !== undefined ) ? size : 1; - - var color = ( hex !== undefined ) ? hex : 0xffff00; - - var width = ( linewidth !== undefined ) ? linewidth : 1; - - // - - var nNormals = 0; - - var objGeometry = this.object.geometry; - - if ( objGeometry && objGeometry.isGeometry ) { - - nNormals = objGeometry.faces.length; - - } else { - - console.warn( 'THREE.FaceNormalsHelper: only THREE.Geometry is supported. Use THREE.VertexNormalsHelper, instead.' ); - - } - - // - - var geometry = new BufferGeometry(); - - var positions = new Float32BufferAttribute( nNormals * 2 * 3, 3 ); - - geometry.addAttribute( 'position', positions ); - - LineSegments.call( this, geometry, new LineBasicMaterial( { color: color, linewidth: width } ) ); - - // - - this.matrixAutoUpdate = false; - this.update(); - -} - -FaceNormalsHelper.prototype = Object.create( LineSegments.prototype ); -FaceNormalsHelper.prototype.constructor = FaceNormalsHelper; - -FaceNormalsHelper.prototype.update = ( function () { - - var v1 = new Vector3(); - var v2 = new Vector3(); - var normalMatrix = new Matrix3(); - - return function update() { - - this.object.updateMatrixWorld( true ); - - normalMatrix.getNormalMatrix( this.object.matrixWorld ); - - var matrixWorld = this.object.matrixWorld; - - var position = this.geometry.attributes.position; - - // - - var objGeometry = this.object.geometry; - - var vertices = objGeometry.vertices; - - var faces = objGeometry.faces; - - var idx = 0; - - for ( var i = 0, l = faces.length; i < l; i ++ ) { - - var face = faces[ i ]; - - var normal = face.normal; - - v1.copy( vertices[ face.a ] ) - .add( vertices[ face.b ] ) - .add( vertices[ face.c ] ) - .divideScalar( 3 ) - .applyMatrix4( matrixWorld ); - - v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 ); - - position.setXYZ( idx, v1.x, v1.y, v1.z ); - - idx = idx + 1; - - position.setXYZ( idx, v2.x, v2.y, v2.z ); - - idx = idx + 1; - - } - - position.needsUpdate = true; - - }; - -}() ); - -/** - * @author alteredq / http://alteredqualia.com/ - * @author mrdoob / http://mrdoob.com/ - * @author WestLangley / http://github.com/WestLangley - */ - -function DirectionalLightHelper( light, size, color ) { - - Object3D.call( this ); - - this.light = light; - this.light.updateMatrixWorld(); - - this.matrix = light.matrixWorld; - this.matrixAutoUpdate = false; - - this.color = color; - - if ( size === undefined ) size = 1; - - var geometry = new BufferGeometry(); - geometry.addAttribute( 'position', new Float32BufferAttribute( [ - - size, size, 0, - size, size, 0, - size, - size, 0, - - size, - size, 0, - - size, size, 0 - ], 3 ) ); - - var material = new LineBasicMaterial( { fog: false } ); - - this.lightPlane = new Line( geometry, material ); - this.add( this.lightPlane ); - - geometry = new BufferGeometry(); - geometry.addAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) ); - - this.targetLine = new Line( geometry, material ); - this.add( this.targetLine ); - - this.update(); - -} - -DirectionalLightHelper.prototype = Object.create( Object3D.prototype ); -DirectionalLightHelper.prototype.constructor = DirectionalLightHelper; - -DirectionalLightHelper.prototype.dispose = function () { - - this.lightPlane.geometry.dispose(); - this.lightPlane.material.dispose(); - this.targetLine.geometry.dispose(); - this.targetLine.material.dispose(); - -}; - -DirectionalLightHelper.prototype.update = function () { - - var v1 = new Vector3(); - var v2 = new Vector3(); - var v3 = new Vector3(); - - return function update() { - - v1.setFromMatrixPosition( this.light.matrixWorld ); - v2.setFromMatrixPosition( this.light.target.matrixWorld ); - v3.subVectors( v2, v1 ); - - this.lightPlane.lookAt( v3 ); - - if ( this.color !== undefined ) { - - this.lightPlane.material.color.set( this.color ); - this.targetLine.material.color.set( this.color ); - - } else { - - this.lightPlane.material.color.copy( this.light.color ); - this.targetLine.material.color.copy( this.light.color ); - - } - - this.targetLine.lookAt( v3 ); - this.targetLine.scale.z = v3.length(); - - }; - -}(); - -/** - * @author alteredq / http://alteredqualia.com/ - * @author Mugen87 / https://github.com/Mugen87 - * - * - shows frustum, line of sight and up of the camera - * - suitable for fast updates - * - based on frustum visualization in lightgl.js shadowmap example - * http://evanw.github.com/lightgl.js/tests/shadowmap.html - */ - -function CameraHelper( camera ) { - - var geometry = new BufferGeometry(); - var material = new LineBasicMaterial( { color: 0xffffff, vertexColors: FaceColors } ); - - var vertices = []; - var colors = []; - - var pointMap = {}; - - // colors - - var colorFrustum = new Color( 0xffaa00 ); - var colorCone = new Color( 0xff0000 ); - var colorUp = new Color( 0x00aaff ); - var colorTarget = new Color( 0xffffff ); - var colorCross = new Color( 0x333333 ); - - // near - - addLine( 'n1', 'n2', colorFrustum ); - addLine( 'n2', 'n4', colorFrustum ); - addLine( 'n4', 'n3', colorFrustum ); - addLine( 'n3', 'n1', colorFrustum ); - - // far - - addLine( 'f1', 'f2', colorFrustum ); - addLine( 'f2', 'f4', colorFrustum ); - addLine( 'f4', 'f3', colorFrustum ); - addLine( 'f3', 'f1', colorFrustum ); - - // sides - - addLine( 'n1', 'f1', colorFrustum ); - addLine( 'n2', 'f2', colorFrustum ); - addLine( 'n3', 'f3', colorFrustum ); - addLine( 'n4', 'f4', colorFrustum ); - - // cone - - addLine( 'p', 'n1', colorCone ); - addLine( 'p', 'n2', colorCone ); - addLine( 'p', 'n3', colorCone ); - addLine( 'p', 'n4', colorCone ); - - // up - - addLine( 'u1', 'u2', colorUp ); - addLine( 'u2', 'u3', colorUp ); - addLine( 'u3', 'u1', colorUp ); - - // target - - addLine( 'c', 't', colorTarget ); - addLine( 'p', 'c', colorCross ); - - // cross - - addLine( 'cn1', 'cn2', colorCross ); - addLine( 'cn3', 'cn4', colorCross ); - - addLine( 'cf1', 'cf2', colorCross ); - addLine( 'cf3', 'cf4', colorCross ); - - function addLine( a, b, color ) { - - addPoint( a, color ); - addPoint( b, color ); - - } - - function addPoint( id, color ) { - - vertices.push( 0, 0, 0 ); - colors.push( color.r, color.g, color.b ); - - if ( pointMap[ id ] === undefined ) { - - pointMap[ id ] = []; - - } - - pointMap[ id ].push( ( vertices.length / 3 ) - 1 ); - - } - - geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - LineSegments.call( this, geometry, material ); - - this.camera = camera; - if ( this.camera.updateProjectionMatrix ) this.camera.updateProjectionMatrix(); - - this.matrix = camera.matrixWorld; - this.matrixAutoUpdate = false; - - this.pointMap = pointMap; - - this.update(); - -} - -CameraHelper.prototype = Object.create( LineSegments.prototype ); -CameraHelper.prototype.constructor = CameraHelper; - -CameraHelper.prototype.update = function () { - - var geometry, pointMap; - - var vector = new Vector3(); - var camera = new Camera(); - - function setPoint( point, x, y, z ) { - - vector.set( x, y, z ).unproject( camera ); - - var points = pointMap[ point ]; - - if ( points !== undefined ) { - - var position = geometry.getAttribute( 'position' ); - - for ( var i = 0, l = points.length; i < l; i ++ ) { - - position.setXYZ( points[ i ], vector.x, vector.y, vector.z ); - - } - - } - - } - - return function update() { - - geometry = this.geometry; - pointMap = this.pointMap; - - var w = 1, h = 1; - - // we need just camera projection matrix - // world matrix must be identity - - camera.projectionMatrix.copy( this.camera.projectionMatrix ); - - // center / target - - setPoint( 'c', 0, 0, - 1 ); - setPoint( 't', 0, 0, 1 ); - - // near - - setPoint( 'n1', - w, - h, - 1 ); - setPoint( 'n2', w, - h, - 1 ); - setPoint( 'n3', - w, h, - 1 ); - setPoint( 'n4', w, h, - 1 ); - - // far - - setPoint( 'f1', - w, - h, 1 ); - setPoint( 'f2', w, - h, 1 ); - setPoint( 'f3', - w, h, 1 ); - setPoint( 'f4', w, h, 1 ); - - // up - - setPoint( 'u1', w * 0.7, h * 1.1, - 1 ); - setPoint( 'u2', - w * 0.7, h * 1.1, - 1 ); - setPoint( 'u3', 0, h * 2, - 1 ); - - // cross - - setPoint( 'cf1', - w, 0, 1 ); - setPoint( 'cf2', w, 0, 1 ); - setPoint( 'cf3', 0, - h, 1 ); - setPoint( 'cf4', 0, h, 1 ); - - setPoint( 'cn1', - w, 0, - 1 ); - setPoint( 'cn2', w, 0, - 1 ); - setPoint( 'cn3', 0, - h, - 1 ); - setPoint( 'cn4', 0, h, - 1 ); - - geometry.getAttribute( 'position' ).needsUpdate = true; - - }; - -}(); - -/** - * @author mrdoob / http://mrdoob.com/ - * @author Mugen87 / http://github.com/Mugen87 - */ - -function BoxHelper( object, color ) { - - this.object = object; - - if ( color === undefined ) color = 0xffff00; - - var indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); - var positions = new Float32Array( 8 * 3 ); - - var geometry = new BufferGeometry(); - geometry.setIndex( new BufferAttribute( indices, 1 ) ); - geometry.addAttribute( 'position', new BufferAttribute( positions, 3 ) ); - - LineSegments.call( this, geometry, new LineBasicMaterial( { color: color } ) ); - - this.matrixAutoUpdate = false; - - this.update(); - -} - -BoxHelper.prototype = Object.create( LineSegments.prototype ); -BoxHelper.prototype.constructor = BoxHelper; - -BoxHelper.prototype.update = ( function () { - - var box = new Box3(); - - return function update( object ) { - - if ( object !== undefined ) { - - console.warn( 'THREE.BoxHelper: .update() has no longer arguments.' ); - - } - - if ( this.object !== undefined ) { - - box.setFromObject( this.object ); - - } - - if ( box.isEmpty() ) return; - - var min = box.min; - var max = box.max; - - /* - 5____4 - 1/___0/| - | 6__|_7 - 2/___3/ - - 0: max.x, max.y, max.z - 1: min.x, max.y, max.z - 2: min.x, min.y, max.z - 3: max.x, min.y, max.z - 4: max.x, max.y, min.z - 5: min.x, max.y, min.z - 6: min.x, min.y, min.z - 7: max.x, min.y, min.z - */ - - var position = this.geometry.attributes.position; - var array = position.array; - - array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z; - array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z; - array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z; - array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z; - array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z; - array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z; - array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z; - array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z; - - position.needsUpdate = true; - - this.geometry.computeBoundingSphere(); - - }; - -} )(); - -BoxHelper.prototype.setFromObject = function ( object ) { - - this.object = object; - this.update(); - - return this; - -}; - -/** - * @author WestLangley / http://github.com/WestLangley - */ - -function Box3Helper( box, hex ) { - - this.type = 'Box3Helper'; - - this.box = box; - - var color = ( hex !== undefined ) ? hex : 0xffff00; - - var indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); - - var positions = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 1, - 1, 1, - 1, - 1 ]; - - var geometry = new BufferGeometry(); - - geometry.setIndex( new BufferAttribute( indices, 1 ) ); - - geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - - LineSegments.call( this, geometry, new LineBasicMaterial( { color: color } ) ); - - this.geometry.computeBoundingSphere(); - -} - -Box3Helper.prototype = Object.create( LineSegments.prototype ); -Box3Helper.prototype.constructor = Box3Helper; - -Box3Helper.prototype.updateMatrixWorld = function ( force ) { - - var box = this.box; - - if ( box.isEmpty() ) return; - - box.getCenter( this.position ); - - box.getSize( this.scale ); - - this.scale.multiplyScalar( 0.5 ); - - Object3D.prototype.updateMatrixWorld.call( this, force ); - -}; - -/** - * @author WestLangley / http://github.com/WestLangley - */ - -function PlaneHelper( plane, size, hex ) { - - this.type = 'PlaneHelper'; - - this.plane = plane; - - this.size = ( size === undefined ) ? 1 : size; - - var color = ( hex !== undefined ) ? hex : 0xffff00; - - var positions = [ 1, - 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0 ]; - - var geometry = new BufferGeometry(); - geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); - geometry.computeBoundingSphere(); - - Line.call( this, geometry, new LineBasicMaterial( { color: color } ) ); - - // - - var positions2 = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, 1, 1, - 1, - 1, 1, 1, - 1, 1 ]; - - var geometry2 = new BufferGeometry(); - geometry2.addAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); - geometry2.computeBoundingSphere(); - - this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false } ) ) ); - -} - -PlaneHelper.prototype = Object.create( Line.prototype ); -PlaneHelper.prototype.constructor = PlaneHelper; - -PlaneHelper.prototype.updateMatrixWorld = function ( force ) { - - var scale = - this.plane.constant; - - if ( Math.abs( scale ) < 1e-8 ) scale = 1e-8; // sign does not matter - - this.scale.set( 0.5 * this.size, 0.5 * this.size, scale ); - - this.lookAt( this.plane.normal ); - - Object3D.prototype.updateMatrixWorld.call( this, force ); - -}; - -/** - * @author WestLangley / http://github.com/WestLangley - * @author zz85 / http://github.com/zz85 - * @author bhouston / http://clara.io - * - * Creates an arrow for visualizing directions - * - * Parameters: - * dir - Vector3 - * origin - Vector3 - * length - Number - * color - color in hex value - * headLength - Number - * headWidth - Number - */ - -var lineGeometry; -var coneGeometry; - -function ArrowHelper( dir, origin, length, color, headLength, headWidth ) { - - // dir is assumed to be normalized - - Object3D.call( this ); - - if ( color === undefined ) color = 0xffff00; - if ( length === undefined ) length = 1; - if ( headLength === undefined ) headLength = 0.2 * length; - if ( headWidth === undefined ) headWidth = 0.2 * headLength; - - if ( lineGeometry === undefined ) { - - lineGeometry = new BufferGeometry(); - lineGeometry.addAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) ); - - coneGeometry = new CylinderBufferGeometry( 0, 0.5, 1, 5, 1 ); - coneGeometry.translate( 0, - 0.5, 0 ); - - } - - this.position.copy( origin ); - - this.line = new Line( lineGeometry, new LineBasicMaterial( { color: color } ) ); - this.line.matrixAutoUpdate = false; - this.add( this.line ); - - this.cone = new Mesh( coneGeometry, new MeshBasicMaterial( { color: color } ) ); - this.cone.matrixAutoUpdate = false; - this.add( this.cone ); - - this.setDirection( dir ); - this.setLength( length, headLength, headWidth ); - -} - -ArrowHelper.prototype = Object.create( Object3D.prototype ); -ArrowHelper.prototype.constructor = ArrowHelper; - -ArrowHelper.prototype.setDirection = ( function () { - - var axis = new Vector3(); - var radians; - - return function setDirection( dir ) { - - // dir is assumed to be normalized - - if ( dir.y > 0.99999 ) { - - this.quaternion.set( 0, 0, 0, 1 ); - - } else if ( dir.y < - 0.99999 ) { - - this.quaternion.set( 1, 0, 0, 0 ); - - } else { - - axis.set( dir.z, 0, - dir.x ).normalize(); - - radians = Math.acos( dir.y ); - - this.quaternion.setFromAxisAngle( axis, radians ); - - } - - }; - -}() ); - -ArrowHelper.prototype.setLength = function ( length, headLength, headWidth ) { - - if ( headLength === undefined ) headLength = 0.2 * length; - if ( headWidth === undefined ) headWidth = 0.2 * headLength; - - this.line.scale.set( 1, Math.max( 0, length - headLength ), 1 ); - this.line.updateMatrix(); - - this.cone.scale.set( headWidth, headLength, headWidth ); - this.cone.position.y = length; - this.cone.updateMatrix(); - -}; - -ArrowHelper.prototype.setColor = function ( color ) { - - this.line.material.color.copy( color ); - this.cone.material.color.copy( color ); - -}; - -/** - * @author sroucheray / http://sroucheray.org/ - * @author mrdoob / http://mrdoob.com/ - */ - -function AxesHelper( size ) { - - size = size || 1; - - var vertices = [ - 0, 0, 0, size, 0, 0, - 0, 0, 0, 0, size, 0, - 0, 0, 0, 0, 0, size - ]; - - var colors = [ - 1, 0, 0, 1, 0.6, 0, - 0, 1, 0, 0.6, 1, 0, - 0, 0, 1, 0, 0.6, 1 - ]; - - var geometry = new BufferGeometry(); - geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); - geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); - - var material = new LineBasicMaterial( { vertexColors: VertexColors } ); - - LineSegments.call( this, geometry, material ); - -} - -AxesHelper.prototype = Object.create( LineSegments.prototype ); -AxesHelper.prototype.constructor = AxesHelper; - -/** - * @author zz85 https://github.com/zz85 - * - * Centripetal CatmullRom Curve - which is useful for avoiding - * cusps and self-intersections in non-uniform catmull rom curves. - * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf - * - * curve.type accepts centripetal(default), chordal and catmullrom - * curve.tension is used for catmullrom which defaults to 0.5 - */ - - -/* -Based on an optimized c++ solution in - - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ - - http://ideone.com/NoEbVM - -This CubicPoly class could be used for reusing some variables and calculations, -but for three.js curve use, it could be possible inlined and flatten into a single function call -which can be placed in CurveUtils. -*/ - -function CubicPoly() { - - var c0 = 0, c1 = 0, c2 = 0, c3 = 0; - - /* - * Compute coefficients for a cubic polynomial - * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 - * such that - * p(0) = x0, p(1) = x1 - * and - * p'(0) = t0, p'(1) = t1. - */ - function init( x0, x1, t0, t1 ) { - - c0 = x0; - c1 = t0; - c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1; - c3 = 2 * x0 - 2 * x1 + t0 + t1; - - } - - return { - - initCatmullRom: function ( x0, x1, x2, x3, tension ) { - - init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); - - }, - - initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { - - // compute tangents when parameterized in [t1,t2] - var t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; - var t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; - - // rescale tangents for parametrization in [0,1] - t1 *= dt1; - t2 *= dt1; - - init( x1, x2, t1, t2 ); - - }, - - calc: function ( t ) { - - var t2 = t * t; - var t3 = t2 * t; - return c0 + c1 * t + c2 * t2 + c3 * t3; - - } - - }; - -} - -// - -var tmp = new Vector3(); -var px = new CubicPoly(); -var py = new CubicPoly(); -var pz = new CubicPoly(); - -function CatmullRomCurve3( points, closed, curveType, tension ) { - - Curve.call( this ); - - this.type = 'CatmullRomCurve3'; - - this.points = points || []; - this.closed = closed || false; - this.curveType = curveType || 'centripetal'; - this.tension = tension || 0.5; - -} - -CatmullRomCurve3.prototype = Object.create( Curve.prototype ); -CatmullRomCurve3.prototype.constructor = CatmullRomCurve3; - -CatmullRomCurve3.prototype.isCatmullRomCurve3 = true; - -CatmullRomCurve3.prototype.getPoint = function ( t, optionalTarget ) { - - var point = optionalTarget || new Vector3(); - - var points = this.points; - var l = points.length; - - var p = ( l - ( this.closed ? 0 : 1 ) ) * t; - var intPoint = Math.floor( p ); - var weight = p - intPoint; - - if ( this.closed ) { - - intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length; - - } else if ( weight === 0 && intPoint === l - 1 ) { - - intPoint = l - 2; - weight = 1; - - } - - var p0, p1, p2, p3; // 4 points - - if ( this.closed || intPoint > 0 ) { - - p0 = points[ ( intPoint - 1 ) % l ]; - - } else { - - // extrapolate first point - tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); - p0 = tmp; - - } - - p1 = points[ intPoint % l ]; - p2 = points[ ( intPoint + 1 ) % l ]; - - if ( this.closed || intPoint + 2 < l ) { - - p3 = points[ ( intPoint + 2 ) % l ]; - - } else { - - // extrapolate last point - tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); - p3 = tmp; - - } - - if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { - - // init Centripetal / Chordal Catmull-Rom - var pow = this.curveType === 'chordal' ? 0.5 : 0.25; - var dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); - var dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); - var dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); - - // safety check for repeated points - if ( dt1 < 1e-4 ) dt1 = 1.0; - if ( dt0 < 1e-4 ) dt0 = dt1; - if ( dt2 < 1e-4 ) dt2 = dt1; - - px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); - py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); - pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); - - } else if ( this.curveType === 'catmullrom' ) { - - px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); - py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); - pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); - - } - - point.set( - px.calc( weight ), - py.calc( weight ), - pz.calc( weight ) - ); - - return point; - -}; - -CatmullRomCurve3.prototype.copy = function ( source ) { - - Curve.prototype.copy.call( this, source ); - - this.points = []; - - for ( var i = 0, l = source.points.length; i < l; i ++ ) { - - var point = source.points[ i ]; - - this.points.push( point.clone() ); - - } - - this.closed = source.closed; - this.curveType = source.curveType; - this.tension = source.tension; - - return this; - -}; - -function CubicBezierCurve3( v0, v1, v2, v3 ) { - - Curve.call( this ); - - this.type = 'CubicBezierCurve3'; - - this.v0 = v0 || new Vector3(); - this.v1 = v1 || new Vector3(); - this.v2 = v2 || new Vector3(); - this.v3 = v3 || new Vector3(); - -} - -CubicBezierCurve3.prototype = Object.create( Curve.prototype ); -CubicBezierCurve3.prototype.constructor = CubicBezierCurve3; - -CubicBezierCurve3.prototype.isCubicBezierCurve3 = true; - -CubicBezierCurve3.prototype.getPoint = function ( t, optionalTarget ) { - - var point = optionalTarget || new Vector3(); - - var v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; - - point.set( - CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), - CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), - CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) - ); - - return point; - -}; - -CubicBezierCurve3.prototype.copy = function ( source ) { - - Curve.prototype.copy.call( this, source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - this.v3.copy( source.v3 ); - - return this; - -}; - -function QuadraticBezierCurve3( v0, v1, v2 ) { - - Curve.call( this ); - - this.type = 'QuadraticBezierCurve3'; - - this.v0 = v0 || new Vector3(); - this.v1 = v1 || new Vector3(); - this.v2 = v2 || new Vector3(); - -} - -QuadraticBezierCurve3.prototype = Object.create( Curve.prototype ); -QuadraticBezierCurve3.prototype.constructor = QuadraticBezierCurve3; - -QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true; - -QuadraticBezierCurve3.prototype.getPoint = function ( t, optionalTarget ) { - - var point = optionalTarget || new Vector3(); - - var v0 = this.v0, v1 = this.v1, v2 = this.v2; - - point.set( - QuadraticBezier( t, v0.x, v1.x, v2.x ), - QuadraticBezier( t, v0.y, v1.y, v2.y ), - QuadraticBezier( t, v0.z, v1.z, v2.z ) - ); - - return point; - -}; - -QuadraticBezierCurve3.prototype.copy = function ( source ) { - - Curve.prototype.copy.call( this, source ); - - this.v0.copy( source.v0 ); - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - -}; - -function LineCurve3( v1, v2 ) { - - Curve.call( this ); - - this.type = 'LineCurve3'; - - this.v1 = v1 || new Vector3(); - this.v2 = v2 || new Vector3(); - -} - -LineCurve3.prototype = Object.create( Curve.prototype ); -LineCurve3.prototype.constructor = LineCurve3; - -LineCurve3.prototype.isLineCurve3 = true; - -LineCurve3.prototype.getPoint = function ( t, optionalTarget ) { - - var point = optionalTarget || new Vector3(); - - if ( t === 1 ) { - - point.copy( this.v2 ); - - } else { - - point.copy( this.v2 ).sub( this.v1 ); - point.multiplyScalar( t ).add( this.v1 ); - - } - - return point; - -}; - -// Line curve is linear, so we can overwrite default getPointAt - -LineCurve3.prototype.getPointAt = function ( u, optionalTarget ) { - - return this.getPoint( u, optionalTarget ); - -}; - -LineCurve3.prototype.copy = function ( source ) { - - Curve.prototype.copy.call( this, source ); - - this.v1.copy( source.v1 ); - this.v2.copy( source.v2 ); - - return this; - -}; - -function ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { - - EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); - - this.type = 'ArcCurve'; - -} - -ArcCurve.prototype = Object.create( EllipseCurve.prototype ); -ArcCurve.prototype.constructor = ArcCurve; - -ArcCurve.prototype.isArcCurve = true; - -/** - * @author alteredq / http://alteredqualia.com/ - */ - -var SceneUtils = { - - createMultiMaterialObject: function ( geometry, materials ) { - - var group = new Group(); - - for ( var i = 0, l = materials.length; i < l; i ++ ) { - - group.add( new Mesh( geometry, materials[ i ] ) ); - - } - - return group; - - }, - - detach: function ( child, parent, scene ) { - - child.applyMatrix( parent.matrixWorld ); - parent.remove( child ); - scene.add( child ); - - }, - - attach: function ( child, scene, parent ) { - - child.applyMatrix( new Matrix4().getInverse( parent.matrixWorld ) ); - - scene.remove( child ); - parent.add( child ); - - } - -}; - -/** - * @author mrdoob / http://mrdoob.com/ - */ - -function Face4( a, b, c, d, normal, color, materialIndex ) { - - console.warn( 'THREE.Face4 has been removed. A THREE.Face3 will be created instead.' ); - return new Face3( a, b, c, normal, color, materialIndex ); - -} - -var LineStrip = 0; - -var LinePieces = 1; - -function MeshFaceMaterial( materials ) { - - console.warn( 'THREE.MeshFaceMaterial has been removed. Use an Array instead.' ); - return materials; - -} - -function MultiMaterial( materials ) { - - if ( materials === undefined ) materials = []; - - console.warn( 'THREE.MultiMaterial has been removed. Use an Array instead.' ); - materials.isMultiMaterial = true; - materials.materials = materials; - materials.clone = function () { - - return materials.slice(); - - }; - return materials; - -} - -function PointCloud( geometry, material ) { - - console.warn( 'THREE.PointCloud has been renamed to THREE.Points.' ); - return new Points( geometry, material ); - -} - -function Particle( material ) { - - console.warn( 'THREE.Particle has been renamed to THREE.Sprite.' ); - return new Sprite( material ); - -} - -function ParticleSystem( geometry, material ) { - - console.warn( 'THREE.ParticleSystem has been renamed to THREE.Points.' ); - return new Points( geometry, material ); - -} - -function PointCloudMaterial( parameters ) { - - console.warn( 'THREE.PointCloudMaterial has been renamed to THREE.PointsMaterial.' ); - return new PointsMaterial( parameters ); - -} - -function ParticleBasicMaterial( parameters ) { - - console.warn( 'THREE.ParticleBasicMaterial has been renamed to THREE.PointsMaterial.' ); - return new PointsMaterial( parameters ); - -} - -function ParticleSystemMaterial( parameters ) { - - console.warn( 'THREE.ParticleSystemMaterial has been renamed to THREE.PointsMaterial.' ); - return new PointsMaterial( parameters ); - -} - -function Vertex( x, y, z ) { - - console.warn( 'THREE.Vertex has been removed. Use THREE.Vector3 instead.' ); - return new Vector3( x, y, z ); - -} - -// - -function DynamicBufferAttribute( array, itemSize ) { - - console.warn( 'THREE.DynamicBufferAttribute has been removed. Use new THREE.BufferAttribute().setDynamic( true ) instead.' ); - return new BufferAttribute( array, itemSize ).setDynamic( true ); - -} - -function Int8Attribute( array, itemSize ) { - - console.warn( 'THREE.Int8Attribute has been removed. Use new THREE.Int8BufferAttribute() instead.' ); - return new Int8BufferAttribute( array, itemSize ); - -} - -function Uint8Attribute( array, itemSize ) { - - console.warn( 'THREE.Uint8Attribute has been removed. Use new THREE.Uint8BufferAttribute() instead.' ); - return new Uint8BufferAttribute( array, itemSize ); - -} - -function Uint8ClampedAttribute( array, itemSize ) { - - console.warn( 'THREE.Uint8ClampedAttribute has been removed. Use new THREE.Uint8ClampedBufferAttribute() instead.' ); - return new Uint8ClampedBufferAttribute( array, itemSize ); - -} - -function Int16Attribute( array, itemSize ) { - - console.warn( 'THREE.Int16Attribute has been removed. Use new THREE.Int16BufferAttribute() instead.' ); - return new Int16BufferAttribute( array, itemSize ); - -} - -function Uint16Attribute( array, itemSize ) { - - console.warn( 'THREE.Uint16Attribute has been removed. Use new THREE.Uint16BufferAttribute() instead.' ); - return new Uint16BufferAttribute( array, itemSize ); - -} - -function Int32Attribute( array, itemSize ) { - - console.warn( 'THREE.Int32Attribute has been removed. Use new THREE.Int32BufferAttribute() instead.' ); - return new Int32BufferAttribute( array, itemSize ); - -} - -function Uint32Attribute( array, itemSize ) { - - console.warn( 'THREE.Uint32Attribute has been removed. Use new THREE.Uint32BufferAttribute() instead.' ); - return new Uint32BufferAttribute( array, itemSize ); - -} - -function Float32Attribute( array, itemSize ) { - - console.warn( 'THREE.Float32Attribute has been removed. Use new THREE.Float32BufferAttribute() instead.' ); - return new Float32BufferAttribute( array, itemSize ); - -} - -function Float64Attribute( array, itemSize ) { - - console.warn( 'THREE.Float64Attribute has been removed. Use new THREE.Float64BufferAttribute() instead.' ); - return new Float64BufferAttribute( array, itemSize ); - -} - -// - -Curve.create = function ( construct, getPoint ) { - - console.log( 'THREE.Curve.create() has been deprecated' ); - - construct.prototype = Object.create( Curve.prototype ); - construct.prototype.constructor = construct; - construct.prototype.getPoint = getPoint; - - return construct; - -}; - -// - -Object.assign( CurvePath.prototype, { - - createPointsGeometry: function ( divisions ) { - - console.warn( 'THREE.CurvePath: .createPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' ); - - // generate geometry from path points (for Line or Points objects) - - var pts = this.getPoints( divisions ); - return this.createGeometry( pts ); - - }, - - createSpacedPointsGeometry: function ( divisions ) { - - console.warn( 'THREE.CurvePath: .createSpacedPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' ); - - // generate geometry from equidistant sampling along the path - - var pts = this.getSpacedPoints( divisions ); - return this.createGeometry( pts ); - - }, - - createGeometry: function ( points ) { - - console.warn( 'THREE.CurvePath: .createGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' ); - - var geometry = new Geometry(); - - for ( var i = 0, l = points.length; i < l; i ++ ) { - - var point = points[ i ]; - geometry.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) ); - - } - - return geometry; - - } - -} ); - -// - -Object.assign( Path.prototype, { - - fromPoints: function ( points ) { - - console.warn( 'THREE.Path: .fromPoints() has been renamed to .setFromPoints().' ); - this.setFromPoints( points ); - - } - -} ); - -// - -function ClosedSplineCurve3( points ) { - - console.warn( 'THREE.ClosedSplineCurve3 has been deprecated. Use THREE.CatmullRomCurve3 instead.' ); - - CatmullRomCurve3.call( this, points ); - this.type = 'catmullrom'; - this.closed = true; - -} - -ClosedSplineCurve3.prototype = Object.create( CatmullRomCurve3.prototype ); - -// - -function SplineCurve3( points ) { - - console.warn( 'THREE.SplineCurve3 has been deprecated. Use THREE.CatmullRomCurve3 instead.' ); - - CatmullRomCurve3.call( this, points ); - this.type = 'catmullrom'; - -} - -SplineCurve3.prototype = Object.create( CatmullRomCurve3.prototype ); - -// - -function Spline( points ) { - - console.warn( 'THREE.Spline has been removed. Use THREE.CatmullRomCurve3 instead.' ); - - CatmullRomCurve3.call( this, points ); - this.type = 'catmullrom'; - -} - -Spline.prototype = Object.create( CatmullRomCurve3.prototype ); - -Object.assign( Spline.prototype, { - - initFromArray: function ( /* a */ ) { - - console.error( 'THREE.Spline: .initFromArray() has been removed.' ); - - }, - getControlPointsArray: function ( /* optionalTarget */ ) { - - console.error( 'THREE.Spline: .getControlPointsArray() has been removed.' ); - - }, - reparametrizeByArcLength: function ( /* samplingCoef */ ) { - - console.error( 'THREE.Spline: .reparametrizeByArcLength() has been removed.' ); - - } - -} ); - -// - -function AxisHelper( size ) { - - console.warn( 'THREE.AxisHelper has been renamed to THREE.AxesHelper.' ); - return new AxesHelper( size ); - -} - -function BoundingBoxHelper( object, color ) { - - console.warn( 'THREE.BoundingBoxHelper has been deprecated. Creating a THREE.BoxHelper instead.' ); - return new BoxHelper( object, color ); - -} - -function EdgesHelper( object, hex ) { - - console.warn( 'THREE.EdgesHelper has been removed. Use THREE.EdgesGeometry instead.' ); - return new LineSegments( new EdgesGeometry( object.geometry ), new LineBasicMaterial( { color: hex !== undefined ? hex : 0xffffff } ) ); - -} - -GridHelper.prototype.setColors = function () { - - console.error( 'THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.' ); - -}; - -SkeletonHelper.prototype.update = function () { - - console.error( 'THREE.SkeletonHelper: update() no longer needs to be called.' ); - -}; - -function WireframeHelper( object, hex ) { - - console.warn( 'THREE.WireframeHelper has been removed. Use THREE.WireframeGeometry instead.' ); - return new LineSegments( new WireframeGeometry( object.geometry ), new LineBasicMaterial( { color: hex !== undefined ? hex : 0xffffff } ) ); - -} - -// - -function XHRLoader( manager ) { - - console.warn( 'THREE.XHRLoader has been renamed to THREE.FileLoader.' ); - return new FileLoader( manager ); - -} - -function BinaryTextureLoader( manager ) { - - console.warn( 'THREE.BinaryTextureLoader has been renamed to THREE.DataTextureLoader.' ); - return new DataTextureLoader( manager ); - -} - -// - -Object.assign( Box2.prototype, { - - center: function ( optionalTarget ) { - - console.warn( 'THREE.Box2: .center() has been renamed to .getCenter().' ); - return this.getCenter( optionalTarget ); - - }, - empty: function () { - - console.warn( 'THREE.Box2: .empty() has been renamed to .isEmpty().' ); - return this.isEmpty(); - - }, - isIntersectionBox: function ( box ) { - - console.warn( 'THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox().' ); - return this.intersectsBox( box ); - - }, - size: function ( optionalTarget ) { - - console.warn( 'THREE.Box2: .size() has been renamed to .getSize().' ); - return this.getSize( optionalTarget ); - - } -} ); - -Object.assign( Box3.prototype, { - - center: function ( optionalTarget ) { - - console.warn( 'THREE.Box3: .center() has been renamed to .getCenter().' ); - return this.getCenter( optionalTarget ); - - }, - empty: function () { - - console.warn( 'THREE.Box3: .empty() has been renamed to .isEmpty().' ); - return this.isEmpty(); - - }, - isIntersectionBox: function ( box ) { - - console.warn( 'THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().' ); - return this.intersectsBox( box ); - - }, - isIntersectionSphere: function ( sphere ) { - - console.warn( 'THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().' ); - return this.intersectsSphere( sphere ); - - }, - size: function ( optionalTarget ) { - - console.warn( 'THREE.Box3: .size() has been renamed to .getSize().' ); - return this.getSize( optionalTarget ); - - } -} ); - -Line3.prototype.center = function ( optionalTarget ) { - - console.warn( 'THREE.Line3: .center() has been renamed to .getCenter().' ); - return this.getCenter( optionalTarget ); - -}; - -Object.assign( _Math, { - - random16: function () { - - console.warn( 'THREE.Math: .random16() has been deprecated. Use Math.random() instead.' ); - return Math.random(); - - }, - - nearestPowerOfTwo: function ( value ) { - - console.warn( 'THREE.Math: .nearestPowerOfTwo() has been renamed to .floorPowerOfTwo().' ); - return _Math.floorPowerOfTwo( value ); - - }, - - nextPowerOfTwo: function ( value ) { - - console.warn( 'THREE.Math: .nextPowerOfTwo() has been renamed to .ceilPowerOfTwo().' ); - return _Math.ceilPowerOfTwo( value ); - - } - -} ); - -Object.assign( Matrix3.prototype, { - - flattenToArrayOffset: function ( array, offset ) { - - console.warn( "THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead." ); - return this.toArray( array, offset ); - - }, - multiplyVector3: function ( vector ) { - - console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' ); - return vector.applyMatrix3( this ); - - }, - multiplyVector3Array: function ( /* a */ ) { - - console.error( 'THREE.Matrix3: .multiplyVector3Array() has been removed.' ); - - }, - applyToBuffer: function ( buffer /*, offset, length */ ) { - - console.warn( 'THREE.Matrix3: .applyToBuffer() has been removed. Use matrix.applyToBufferAttribute( attribute ) instead.' ); - return this.applyToBufferAttribute( buffer ); - - }, - applyToVector3Array: function ( /* array, offset, length */ ) { - - console.error( 'THREE.Matrix3: .applyToVector3Array() has been removed.' ); - - } - -} ); - -Object.assign( Matrix4.prototype, { - - extractPosition: function ( m ) { - - console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' ); - return this.copyPosition( m ); - - }, - flattenToArrayOffset: function ( array, offset ) { - - console.warn( "THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead." ); - return this.toArray( array, offset ); - - }, - getPosition: function () { - - var v1; - - return function getPosition() { - - if ( v1 === undefined ) v1 = new Vector3(); - console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' ); - return v1.setFromMatrixColumn( this, 3 ); - - }; - - }(), - setRotationFromQuaternion: function ( q ) { - - console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' ); - return this.makeRotationFromQuaternion( q ); - - }, - multiplyToArray: function () { - - console.warn( 'THREE.Matrix4: .multiplyToArray() has been removed.' ); - - }, - multiplyVector3: function ( vector ) { - - console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); - return vector.applyMatrix4( this ); - - }, - multiplyVector4: function ( vector ) { - - console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); - return vector.applyMatrix4( this ); - - }, - multiplyVector3Array: function ( /* a */ ) { - - console.error( 'THREE.Matrix4: .multiplyVector3Array() has been removed.' ); - - }, - rotateAxis: function ( v ) { - - console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' ); - v.transformDirection( this ); - - }, - crossVector: function ( vector ) { - - console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); - return vector.applyMatrix4( this ); - - }, - translate: function () { - - console.error( 'THREE.Matrix4: .translate() has been removed.' ); - - }, - rotateX: function () { - - console.error( 'THREE.Matrix4: .rotateX() has been removed.' ); - - }, - rotateY: function () { - - console.error( 'THREE.Matrix4: .rotateY() has been removed.' ); - - }, - rotateZ: function () { - - console.error( 'THREE.Matrix4: .rotateZ() has been removed.' ); - - }, - rotateByAxis: function () { - - console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' ); - - }, - applyToBuffer: function ( buffer /*, offset, length */ ) { - - console.warn( 'THREE.Matrix4: .applyToBuffer() has been removed. Use matrix.applyToBufferAttribute( attribute ) instead.' ); - return this.applyToBufferAttribute( buffer ); - - }, - applyToVector3Array: function ( /* array, offset, length */ ) { - - console.error( 'THREE.Matrix4: .applyToVector3Array() has been removed.' ); - - }, - makeFrustum: function ( left, right, bottom, top, near, far ) { - - console.warn( 'THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead.' ); - return this.makePerspective( left, right, top, bottom, near, far ); - - } - -} ); - -Plane.prototype.isIntersectionLine = function ( line ) { - - console.warn( 'THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().' ); - return this.intersectsLine( line ); - -}; - -Quaternion.prototype.multiplyVector3 = function ( vector ) { - - console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' ); - return vector.applyQuaternion( this ); - -}; - -Object.assign( Ray.prototype, { - - isIntersectionBox: function ( box ) { - - console.warn( 'THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().' ); - return this.intersectsBox( box ); - - }, - isIntersectionPlane: function ( plane ) { - - console.warn( 'THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().' ); - return this.intersectsPlane( plane ); - - }, - isIntersectionSphere: function ( sphere ) { - - console.warn( 'THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().' ); - return this.intersectsSphere( sphere ); - - } - -} ); - -Object.assign( Shape.prototype, { - - extractAllPoints: function ( divisions ) { - - console.warn( 'THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.' ); - return this.extractPoints( divisions ); - - }, - extrude: function ( options ) { - - console.warn( 'THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.' ); - return new ExtrudeGeometry( this, options ); - - }, - makeGeometry: function ( options ) { - - console.warn( 'THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.' ); - return new ShapeGeometry( this, options ); - - } - -} ); - -Object.assign( Vector2.prototype, { - - fromAttribute: function ( attribute, index, offset ) { - - console.warn( 'THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().' ); - return this.fromBufferAttribute( attribute, index, offset ); - - }, - distanceToManhattan: function ( v ) { - - console.warn( 'THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' ); - return this.manhattanDistanceTo( v ); - - }, - lengthManhattan: function () { - - console.warn( 'THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().' ); - return this.manhattanLength(); - - } - -} ); - -Object.assign( Vector3.prototype, { - - setEulerFromRotationMatrix: function () { - - console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' ); - - }, - setEulerFromQuaternion: function () { - - console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' ); - - }, - getPositionFromMatrix: function ( m ) { - - console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' ); - return this.setFromMatrixPosition( m ); - - }, - getScaleFromMatrix: function ( m ) { - - console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' ); - return this.setFromMatrixScale( m ); - - }, - getColumnFromMatrix: function ( index, matrix ) { - - console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' ); - return this.setFromMatrixColumn( matrix, index ); - - }, - applyProjection: function ( m ) { - - console.warn( 'THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.' ); - return this.applyMatrix4( m ); - - }, - fromAttribute: function ( attribute, index, offset ) { - - console.warn( 'THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().' ); - return this.fromBufferAttribute( attribute, index, offset ); - - }, - distanceToManhattan: function ( v ) { - - console.warn( 'THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' ); - return this.manhattanDistanceTo( v ); - - }, - lengthManhattan: function () { - - console.warn( 'THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength().' ); - return this.manhattanLength(); - - } - -} ); - -Object.assign( Vector4.prototype, { - - fromAttribute: function ( attribute, index, offset ) { - - console.warn( 'THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute().' ); - return this.fromBufferAttribute( attribute, index, offset ); - - }, - lengthManhattan: function () { - - console.warn( 'THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength().' ); - return this.manhattanLength(); - - } - -} ); - -// - -Geometry.prototype.computeTangents = function () { - - console.warn( 'THREE.Geometry: .computeTangents() has been removed.' ); - -}; - -Object.assign( Object3D.prototype, { - - getChildByName: function ( name ) { - - console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' ); - return this.getObjectByName( name ); - - }, - renderDepth: function () { - - console.warn( 'THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.' ); - - }, - translate: function ( distance, axis ) { - - console.warn( 'THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.' ); - return this.translateOnAxis( axis, distance ); - - } - -} ); - -Object.defineProperties( Object3D.prototype, { - - eulerOrder: { - get: function () { - - console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' ); - return this.rotation.order; - - }, - set: function ( value ) { - - console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' ); - this.rotation.order = value; - - } - }, - useQuaternion: { - get: function () { - - console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' ); - - }, - set: function () { - - console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' ); - - } - } - -} ); - -Object.defineProperties( LOD.prototype, { - - objects: { - get: function () { - - console.warn( 'THREE.LOD: .objects has been renamed to .levels.' ); - return this.levels; - - } - } - -} ); - -Object.defineProperty( Skeleton.prototype, 'useVertexTexture', { - - get: function () { - - console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' ); - - }, - set: function () { - - console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' ); - - } - -} ); - -Object.defineProperty( Curve.prototype, '__arcLengthDivisions', { - - get: function () { - - console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' ); - return this.arcLengthDivisions; - - }, - set: function ( value ) { - - console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' ); - this.arcLengthDivisions = value; - - } - -} ); - -// - -PerspectiveCamera.prototype.setLens = function ( focalLength, filmGauge ) { - - console.warn( "THREE.PerspectiveCamera.setLens is deprecated. " + - "Use .setFocalLength and .filmGauge for a photographic setup." ); - - if ( filmGauge !== undefined ) this.filmGauge = filmGauge; - this.setFocalLength( focalLength ); - -}; - -// - -Object.defineProperties( Light.prototype, { - onlyShadow: { - set: function () { - - console.warn( 'THREE.Light: .onlyShadow has been removed.' ); - - } - }, - shadowCameraFov: { - set: function ( value ) { - - console.warn( 'THREE.Light: .shadowCameraFov is now .shadow.camera.fov.' ); - this.shadow.camera.fov = value; - - } - }, - shadowCameraLeft: { - set: function ( value ) { - - console.warn( 'THREE.Light: .shadowCameraLeft is now .shadow.camera.left.' ); - this.shadow.camera.left = value; - - } - }, - shadowCameraRight: { - set: function ( value ) { - - console.warn( 'THREE.Light: .shadowCameraRight is now .shadow.camera.right.' ); - this.shadow.camera.right = value; - - } - }, - shadowCameraTop: { - set: function ( value ) { - - console.warn( 'THREE.Light: .shadowCameraTop is now .shadow.camera.top.' ); - this.shadow.camera.top = value; - - } - }, - shadowCameraBottom: { - set: function ( value ) { - - console.warn( 'THREE.Light: .shadowCameraBottom is now .shadow.camera.bottom.' ); - this.shadow.camera.bottom = value; - - } - }, - shadowCameraNear: { - set: function ( value ) { - - console.warn( 'THREE.Light: .shadowCameraNear is now .shadow.camera.near.' ); - this.shadow.camera.near = value; - - } - }, - shadowCameraFar: { - set: function ( value ) { - - console.warn( 'THREE.Light: .shadowCameraFar is now .shadow.camera.far.' ); - this.shadow.camera.far = value; - - } - }, - shadowCameraVisible: { - set: function () { - - console.warn( 'THREE.Light: .shadowCameraVisible has been removed. Use new THREE.CameraHelper( light.shadow.camera ) instead.' ); - - } - }, - shadowBias: { - set: function ( value ) { - - console.warn( 'THREE.Light: .shadowBias is now .shadow.bias.' ); - this.shadow.bias = value; - - } - }, - shadowDarkness: { - set: function () { - - console.warn( 'THREE.Light: .shadowDarkness has been removed.' ); - - } - }, - shadowMapWidth: { - set: function ( value ) { - - console.warn( 'THREE.Light: .shadowMapWidth is now .shadow.mapSize.width.' ); - this.shadow.mapSize.width = value; - - } - }, - shadowMapHeight: { - set: function ( value ) { - - console.warn( 'THREE.Light: .shadowMapHeight is now .shadow.mapSize.height.' ); - this.shadow.mapSize.height = value; - - } - } -} ); - -// - -Object.defineProperties( BufferAttribute.prototype, { - - length: { - get: function () { - - console.warn( 'THREE.BufferAttribute: .length has been deprecated. Use .count instead.' ); - return this.array.length; - - } - } - -} ); - -Object.assign( BufferGeometry.prototype, { - - addIndex: function ( index ) { - - console.warn( 'THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().' ); - this.setIndex( index ); - - }, - addDrawCall: function ( start, count, indexOffset ) { - - if ( indexOffset !== undefined ) { - - console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' ); - - } - console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' ); - this.addGroup( start, count ); - - }, - clearDrawCalls: function () { - - console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' ); - this.clearGroups(); - - }, - computeTangents: function () { - - console.warn( 'THREE.BufferGeometry: .computeTangents() has been removed.' ); - - }, - computeOffsets: function () { - - console.warn( 'THREE.BufferGeometry: .computeOffsets() has been removed.' ); - - } - -} ); - -Object.defineProperties( BufferGeometry.prototype, { - - drawcalls: { - get: function () { - - console.error( 'THREE.BufferGeometry: .drawcalls has been renamed to .groups.' ); - return this.groups; - - } - }, - offsets: { - get: function () { - - console.warn( 'THREE.BufferGeometry: .offsets has been renamed to .groups.' ); - return this.groups; - - } - } - -} ); - -// - -Object.defineProperties( Uniform.prototype, { - - dynamic: { - set: function () { - - console.warn( 'THREE.Uniform: .dynamic has been removed. Use object.onBeforeRender() instead.' ); - - } - }, - onUpdate: { - value: function () { - - console.warn( 'THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead.' ); - return this; - - } - } - -} ); - -// - -Object.defineProperties( Material.prototype, { - - wrapAround: { - get: function () { - - console.warn( 'THREE.Material: .wrapAround has been removed.' ); - - }, - set: function () { - - console.warn( 'THREE.Material: .wrapAround has been removed.' ); - - } - }, - wrapRGB: { - get: function () { - - console.warn( 'THREE.Material: .wrapRGB has been removed.' ); - return new Color(); - - } - }, - - shading: { - get: function () { - - console.error( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' ); - - }, - set: function ( value ) { - - console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' ); - this.flatShading = ( value === FlatShading ); - - } - } - -} ); - -Object.defineProperties( MeshPhongMaterial.prototype, { - - metal: { - get: function () { - - console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead.' ); - return false; - - }, - set: function () { - - console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead' ); - - } - } - -} ); - -Object.defineProperties( ShaderMaterial.prototype, { - - derivatives: { - get: function () { - - console.warn( 'THREE.ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' ); - return this.extensions.derivatives; - - }, - set: function ( value ) { - - console.warn( 'THREE. ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' ); - this.extensions.derivatives = value; - - } - } - -} ); - -// - -Object.assign( WebGLRenderer.prototype, { - - getCurrentRenderTarget: function () { - - console.warn( 'THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget().' ); - return this.getRenderTarget(); - - }, - - getMaxAnisotropy: function () { - - console.warn( 'THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy().' ); - return this.capabilities.getMaxAnisotropy(); - - }, - - getPrecision: function () { - - console.warn( 'THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision.' ); - return this.capabilities.precision; - - }, - - resetGLState: function () { - - console.warn( 'THREE.WebGLRenderer: .resetGLState() is now .state.reset().' ); - return this.state.reset(); - - }, - - supportsFloatTextures: function () { - - console.warn( 'THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( \'OES_texture_float\' ).' ); - return this.extensions.get( 'OES_texture_float' ); - - }, - supportsHalfFloatTextures: function () { - - console.warn( 'THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( \'OES_texture_half_float\' ).' ); - return this.extensions.get( 'OES_texture_half_float' ); - - }, - supportsStandardDerivatives: function () { - - console.warn( 'THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( \'OES_standard_derivatives\' ).' ); - return this.extensions.get( 'OES_standard_derivatives' ); - - }, - supportsCompressedTextureS3TC: function () { - - console.warn( 'THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( \'WEBGL_compressed_texture_s3tc\' ).' ); - return this.extensions.get( 'WEBGL_compressed_texture_s3tc' ); - - }, - supportsCompressedTexturePVRTC: function () { - - console.warn( 'THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( \'WEBGL_compressed_texture_pvrtc\' ).' ); - return this.extensions.get( 'WEBGL_compressed_texture_pvrtc' ); - - }, - supportsBlendMinMax: function () { - - console.warn( 'THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( \'EXT_blend_minmax\' ).' ); - return this.extensions.get( 'EXT_blend_minmax' ); - - }, - supportsVertexTextures: function () { - - console.warn( 'THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures.' ); - return this.capabilities.vertexTextures; - - }, - supportsInstancedArrays: function () { - - console.warn( 'THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( \'ANGLE_instanced_arrays\' ).' ); - return this.extensions.get( 'ANGLE_instanced_arrays' ); - - }, - enableScissorTest: function ( boolean ) { - - console.warn( 'THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest().' ); - this.setScissorTest( boolean ); - - }, - initMaterial: function () { - - console.warn( 'THREE.WebGLRenderer: .initMaterial() has been removed.' ); - - }, - addPrePlugin: function () { - - console.warn( 'THREE.WebGLRenderer: .addPrePlugin() has been removed.' ); - - }, - addPostPlugin: function () { - - console.warn( 'THREE.WebGLRenderer: .addPostPlugin() has been removed.' ); - - }, - updateShadowMap: function () { - - console.warn( 'THREE.WebGLRenderer: .updateShadowMap() has been removed.' ); - - } - -} ); - -Object.defineProperties( WebGLRenderer.prototype, { - - shadowMapEnabled: { - get: function () { - - return this.shadowMap.enabled; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled.' ); - this.shadowMap.enabled = value; - - } - }, - shadowMapType: { - get: function () { - - return this.shadowMap.type; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type.' ); - this.shadowMap.type = value; - - } - }, - shadowMapCullFace: { - get: function () { - - return this.shadowMap.cullFace; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace is now .shadowMap.cullFace.' ); - this.shadowMap.cullFace = value; - - } - } -} ); - -Object.defineProperties( WebGLShadowMap.prototype, { - - cullFace: { - get: function () { - - return this.renderReverseSided ? CullFaceFront : CullFaceBack; - - }, - set: function ( cullFace ) { - - var value = ( cullFace !== CullFaceBack ); - console.warn( "WebGLRenderer: .shadowMap.cullFace is deprecated. Set .shadowMap.renderReverseSided to " + value + "." ); - this.renderReverseSided = value; - - } - } - -} ); - -// - -Object.defineProperties( WebGLRenderTarget.prototype, { - - wrapS: { - get: function () { - - console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' ); - return this.texture.wrapS; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' ); - this.texture.wrapS = value; - - } - }, - wrapT: { - get: function () { - - console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' ); - return this.texture.wrapT; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' ); - this.texture.wrapT = value; - - } - }, - magFilter: { - get: function () { - - console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' ); - return this.texture.magFilter; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' ); - this.texture.magFilter = value; - - } - }, - minFilter: { - get: function () { - - console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' ); - return this.texture.minFilter; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' ); - this.texture.minFilter = value; - - } - }, - anisotropy: { - get: function () { - - console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' ); - return this.texture.anisotropy; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' ); - this.texture.anisotropy = value; - - } - }, - offset: { - get: function () { - - console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' ); - return this.texture.offset; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' ); - this.texture.offset = value; - - } - }, - repeat: { - get: function () { - - console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' ); - return this.texture.repeat; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' ); - this.texture.repeat = value; - - } - }, - format: { - get: function () { - - console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' ); - return this.texture.format; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' ); - this.texture.format = value; - - } - }, - type: { - get: function () { - - console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' ); - return this.texture.type; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' ); - this.texture.type = value; - - } - }, - generateMipmaps: { - get: function () { - - console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' ); - return this.texture.generateMipmaps; - - }, - set: function ( value ) { - - console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' ); - this.texture.generateMipmaps = value; - - } - } - -} ); - -// - -Audio.prototype.load = function ( file ) { - - console.warn( 'THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.' ); - var scope = this; - var audioLoader = new AudioLoader(); - audioLoader.load( file, function ( buffer ) { - - scope.setBuffer( buffer ); - - } ); - return this; - -}; - -AudioAnalyser.prototype.getData = function () { - - console.warn( 'THREE.AudioAnalyser: .getData() is now .getFrequencyData().' ); - return this.getFrequencyData(); - -}; - -// - -CubeCamera.prototype.updateCubeMap = function ( renderer, scene ) { - - console.warn( 'THREE.CubeCamera: .updateCubeMap() is now .update().' ); - return this.update( renderer, scene ); - -}; - -// - -var GeometryUtils = { - - merge: function ( geometry1, geometry2, materialIndexOffset ) { - - console.warn( 'THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.' ); - var matrix; - - if ( geometry2.isMesh ) { - - geometry2.matrixAutoUpdate && geometry2.updateMatrix(); - - matrix = geometry2.matrix; - geometry2 = geometry2.geometry; - - } - - geometry1.merge( geometry2, matrix, materialIndexOffset ); - - }, - - center: function ( geometry ) { - - console.warn( 'THREE.GeometryUtils: .center() has been moved to Geometry. Use geometry.center() instead.' ); - return geometry.center(); - - } - -}; - -var ImageUtils = { - - crossOrigin: undefined, - - loadTexture: function ( url, mapping, onLoad, onError ) { - - console.warn( 'THREE.ImageUtils.loadTexture has been deprecated. Use THREE.TextureLoader() instead.' ); - - var loader = new TextureLoader(); - loader.setCrossOrigin( this.crossOrigin ); - - var texture = loader.load( url, onLoad, undefined, onError ); - - if ( mapping ) texture.mapping = mapping; - - return texture; - - }, - - loadTextureCube: function ( urls, mapping, onLoad, onError ) { - - console.warn( 'THREE.ImageUtils.loadTextureCube has been deprecated. Use THREE.CubeTextureLoader() instead.' ); - - var loader = new CubeTextureLoader(); - loader.setCrossOrigin( this.crossOrigin ); - - var texture = loader.load( urls, onLoad, undefined, onError ); - - if ( mapping ) texture.mapping = mapping; - - return texture; - - }, - - loadCompressedTexture: function () { - - console.error( 'THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.' ); - - }, - - loadCompressedTextureCube: function () { - - console.error( 'THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.' ); - - } - -}; - -// - -function Projector() { - - console.error( 'THREE.Projector has been moved to /examples/js/renderers/Projector.js.' ); - - this.projectVector = function ( vector, camera ) { - - console.warn( 'THREE.Projector: .projectVector() is now vector.project().' ); - vector.project( camera ); - - }; - - this.unprojectVector = function ( vector, camera ) { - - console.warn( 'THREE.Projector: .unprojectVector() is now vector.unproject().' ); - vector.unproject( camera ); - - }; - - this.pickingRay = function () { - - console.error( 'THREE.Projector: .pickingRay() is now raycaster.setFromCamera().' ); - - }; - -} - -// - -function CanvasRenderer() { - - console.error( 'THREE.CanvasRenderer has been moved to /examples/js/renderers/CanvasRenderer.js' ); - - this.domElement = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); - this.clear = function () {}; - this.render = function () {}; - this.setClearColor = function () {}; - this.setSize = function () {}; - -} - - -var THREE = Object.freeze({ - WebGLRenderTargetCube: WebGLRenderTargetCube, - WebGLRenderTarget: WebGLRenderTarget, - WebGLRenderer: WebGLRenderer, - ShaderLib: ShaderLib, - UniformsLib: UniformsLib, - UniformsUtils: UniformsUtils, - ShaderChunk: ShaderChunk, - FogExp2: FogExp2, - Fog: Fog, - Scene: Scene, - LensFlare: LensFlare, - Sprite: Sprite, - LOD: LOD, - SkinnedMesh: SkinnedMesh, - Skeleton: Skeleton, - Bone: Bone, - Mesh: Mesh, - LineSegments: LineSegments, - LineLoop: LineLoop, - Line: Line, - Points: Points, - Group: Group, - VideoTexture: VideoTexture, - DataTexture: DataTexture, - CompressedTexture: CompressedTexture, - CubeTexture: CubeTexture, - CanvasTexture: CanvasTexture, - DepthTexture: DepthTexture, - Texture: Texture, - CompressedTextureLoader: CompressedTextureLoader, - DataTextureLoader: DataTextureLoader, - CubeTextureLoader: CubeTextureLoader, - TextureLoader: TextureLoader, - ObjectLoader: ObjectLoader, - MaterialLoader: MaterialLoader, - BufferGeometryLoader: BufferGeometryLoader, - DefaultLoadingManager: DefaultLoadingManager, - LoadingManager: LoadingManager, - JSONLoader: JSONLoader, - ImageLoader: ImageLoader, - FontLoader: FontLoader, - FileLoader: FileLoader, - Loader: Loader, - Cache: Cache, - AudioLoader: AudioLoader, - SpotLightShadow: SpotLightShadow, - SpotLight: SpotLight, - PointLight: PointLight, - RectAreaLight: RectAreaLight, - HemisphereLight: HemisphereLight, - DirectionalLightShadow: DirectionalLightShadow, - DirectionalLight: DirectionalLight, - AmbientLight: AmbientLight, - LightShadow: LightShadow, - Light: Light, - StereoCamera: StereoCamera, - PerspectiveCamera: PerspectiveCamera, - OrthographicCamera: OrthographicCamera, - CubeCamera: CubeCamera, - ArrayCamera: ArrayCamera, - Camera: Camera, - AudioListener: AudioListener, - PositionalAudio: PositionalAudio, - AudioContext: AudioContext, - AudioAnalyser: AudioAnalyser, - Audio: Audio, - VectorKeyframeTrack: VectorKeyframeTrack, - StringKeyframeTrack: StringKeyframeTrack, - QuaternionKeyframeTrack: QuaternionKeyframeTrack, - NumberKeyframeTrack: NumberKeyframeTrack, - ColorKeyframeTrack: ColorKeyframeTrack, - BooleanKeyframeTrack: BooleanKeyframeTrack, - PropertyMixer: PropertyMixer, - PropertyBinding: PropertyBinding, - KeyframeTrack: KeyframeTrack, - AnimationUtils: AnimationUtils, - AnimationObjectGroup: AnimationObjectGroup, - AnimationMixer: AnimationMixer, - AnimationClip: AnimationClip, - Uniform: Uniform, - InstancedBufferGeometry: InstancedBufferGeometry, - BufferGeometry: BufferGeometry, - Geometry: Geometry, - InterleavedBufferAttribute: InterleavedBufferAttribute, - InstancedInterleavedBuffer: InstancedInterleavedBuffer, - InterleavedBuffer: InterleavedBuffer, - InstancedBufferAttribute: InstancedBufferAttribute, - Face3: Face3, - Object3D: Object3D, - Raycaster: Raycaster, - Layers: Layers, - EventDispatcher: EventDispatcher, - Clock: Clock, - QuaternionLinearInterpolant: QuaternionLinearInterpolant, - LinearInterpolant: LinearInterpolant, - DiscreteInterpolant: DiscreteInterpolant, - CubicInterpolant: CubicInterpolant, - Interpolant: Interpolant, - Triangle: Triangle, - Math: _Math, - Spherical: Spherical, - Cylindrical: Cylindrical, - Plane: Plane, - Frustum: Frustum, - Sphere: Sphere, - Ray: Ray, - Matrix4: Matrix4, - Matrix3: Matrix3, - Box3: Box3, - Box2: Box2, - Line3: Line3, - Euler: Euler, - Vector4: Vector4, - Vector3: Vector3, - Vector2: Vector2, - Quaternion: Quaternion, - Color: Color, - ImmediateRenderObject: ImmediateRenderObject, - VertexNormalsHelper: VertexNormalsHelper, - SpotLightHelper: SpotLightHelper, - SkeletonHelper: SkeletonHelper, - PointLightHelper: PointLightHelper, - RectAreaLightHelper: RectAreaLightHelper, - HemisphereLightHelper: HemisphereLightHelper, - GridHelper: GridHelper, - PolarGridHelper: PolarGridHelper, - FaceNormalsHelper: FaceNormalsHelper, - DirectionalLightHelper: DirectionalLightHelper, - CameraHelper: CameraHelper, - BoxHelper: BoxHelper, - Box3Helper: Box3Helper, - PlaneHelper: PlaneHelper, - ArrowHelper: ArrowHelper, - AxesHelper: AxesHelper, - CatmullRomCurve3: CatmullRomCurve3, - CubicBezierCurve3: CubicBezierCurve3, - QuadraticBezierCurve3: QuadraticBezierCurve3, - LineCurve3: LineCurve3, - ArcCurve: ArcCurve, - EllipseCurve: EllipseCurve, - SplineCurve: SplineCurve, - CubicBezierCurve: CubicBezierCurve, - QuadraticBezierCurve: QuadraticBezierCurve, - LineCurve: LineCurve, - Shape: Shape, - Path: Path, - ShapePath: ShapePath, - Font: Font, - CurvePath: CurvePath, - Curve: Curve, - ShapeUtils: ShapeUtils, - SceneUtils: SceneUtils, - WebGLUtils: WebGLUtils, - WireframeGeometry: WireframeGeometry, - ParametricGeometry: ParametricGeometry, - ParametricBufferGeometry: ParametricBufferGeometry, - TetrahedronGeometry: TetrahedronGeometry, - TetrahedronBufferGeometry: TetrahedronBufferGeometry, - OctahedronGeometry: OctahedronGeometry, - OctahedronBufferGeometry: OctahedronBufferGeometry, - IcosahedronGeometry: IcosahedronGeometry, - IcosahedronBufferGeometry: IcosahedronBufferGeometry, - DodecahedronGeometry: DodecahedronGeometry, - DodecahedronBufferGeometry: DodecahedronBufferGeometry, - PolyhedronGeometry: PolyhedronGeometry, - PolyhedronBufferGeometry: PolyhedronBufferGeometry, - TubeGeometry: TubeGeometry, - TubeBufferGeometry: TubeBufferGeometry, - TorusKnotGeometry: TorusKnotGeometry, - TorusKnotBufferGeometry: TorusKnotBufferGeometry, - TorusGeometry: TorusGeometry, - TorusBufferGeometry: TorusBufferGeometry, - TextGeometry: TextGeometry, - TextBufferGeometry: TextBufferGeometry, - SphereGeometry: SphereGeometry, - SphereBufferGeometry: SphereBufferGeometry, - RingGeometry: RingGeometry, - RingBufferGeometry: RingBufferGeometry, - PlaneGeometry: PlaneGeometry, - PlaneBufferGeometry: PlaneBufferGeometry, - LatheGeometry: LatheGeometry, - LatheBufferGeometry: LatheBufferGeometry, - ShapeGeometry: ShapeGeometry, - ShapeBufferGeometry: ShapeBufferGeometry, - ExtrudeGeometry: ExtrudeGeometry, - ExtrudeBufferGeometry: ExtrudeBufferGeometry, - EdgesGeometry: EdgesGeometry, - ConeGeometry: ConeGeometry, - ConeBufferGeometry: ConeBufferGeometry, - CylinderGeometry: CylinderGeometry, - CylinderBufferGeometry: CylinderBufferGeometry, - CircleGeometry: CircleGeometry, - CircleBufferGeometry: CircleBufferGeometry, - BoxGeometry: BoxGeometry, - BoxBufferGeometry: BoxBufferGeometry, - ShadowMaterial: ShadowMaterial, - SpriteMaterial: SpriteMaterial, - RawShaderMaterial: RawShaderMaterial, - ShaderMaterial: ShaderMaterial, - PointsMaterial: PointsMaterial, - MeshPhysicalMaterial: MeshPhysicalMaterial, - MeshStandardMaterial: MeshStandardMaterial, - MeshPhongMaterial: MeshPhongMaterial, - MeshToonMaterial: MeshToonMaterial, - MeshNormalMaterial: MeshNormalMaterial, - MeshLambertMaterial: MeshLambertMaterial, - MeshDepthMaterial: MeshDepthMaterial, - MeshDistanceMaterial: MeshDistanceMaterial, - MeshBasicMaterial: MeshBasicMaterial, - LineDashedMaterial: LineDashedMaterial, - LineBasicMaterial: LineBasicMaterial, - Material: Material, - Float64BufferAttribute: Float64BufferAttribute, - Float32BufferAttribute: Float32BufferAttribute, - Uint32BufferAttribute: Uint32BufferAttribute, - Int32BufferAttribute: Int32BufferAttribute, - Uint16BufferAttribute: Uint16BufferAttribute, - Int16BufferAttribute: Int16BufferAttribute, - Uint8ClampedBufferAttribute: Uint8ClampedBufferAttribute, - Uint8BufferAttribute: Uint8BufferAttribute, - Int8BufferAttribute: Int8BufferAttribute, - BufferAttribute: BufferAttribute, - REVISION: REVISION, - MOUSE: MOUSE, - CullFaceNone: CullFaceNone, - CullFaceBack: CullFaceBack, - CullFaceFront: CullFaceFront, - CullFaceFrontBack: CullFaceFrontBack, - FrontFaceDirectionCW: FrontFaceDirectionCW, - FrontFaceDirectionCCW: FrontFaceDirectionCCW, - BasicShadowMap: BasicShadowMap, - PCFShadowMap: PCFShadowMap, - PCFSoftShadowMap: PCFSoftShadowMap, - FrontSide: FrontSide, - BackSide: BackSide, - DoubleSide: DoubleSide, - FlatShading: FlatShading, - SmoothShading: SmoothShading, - NoColors: NoColors, - FaceColors: FaceColors, - VertexColors: VertexColors, - NoBlending: NoBlending, - NormalBlending: NormalBlending, - AdditiveBlending: AdditiveBlending, - SubtractiveBlending: SubtractiveBlending, - MultiplyBlending: MultiplyBlending, - CustomBlending: CustomBlending, - AddEquation: AddEquation, - SubtractEquation: SubtractEquation, - ReverseSubtractEquation: ReverseSubtractEquation, - MinEquation: MinEquation, - MaxEquation: MaxEquation, - ZeroFactor: ZeroFactor, - OneFactor: OneFactor, - SrcColorFactor: SrcColorFactor, - OneMinusSrcColorFactor: OneMinusSrcColorFactor, - SrcAlphaFactor: SrcAlphaFactor, - OneMinusSrcAlphaFactor: OneMinusSrcAlphaFactor, - DstAlphaFactor: DstAlphaFactor, - OneMinusDstAlphaFactor: OneMinusDstAlphaFactor, - DstColorFactor: DstColorFactor, - OneMinusDstColorFactor: OneMinusDstColorFactor, - SrcAlphaSaturateFactor: SrcAlphaSaturateFactor, - NeverDepth: NeverDepth, - AlwaysDepth: AlwaysDepth, - LessDepth: LessDepth, - LessEqualDepth: LessEqualDepth, - EqualDepth: EqualDepth, - GreaterEqualDepth: GreaterEqualDepth, - GreaterDepth: GreaterDepth, - NotEqualDepth: NotEqualDepth, - MultiplyOperation: MultiplyOperation, - MixOperation: MixOperation, - AddOperation: AddOperation, - NoToneMapping: NoToneMapping, - LinearToneMapping: LinearToneMapping, - ReinhardToneMapping: ReinhardToneMapping, - Uncharted2ToneMapping: Uncharted2ToneMapping, - CineonToneMapping: CineonToneMapping, - UVMapping: UVMapping, - CubeReflectionMapping: CubeReflectionMapping, - CubeRefractionMapping: CubeRefractionMapping, - EquirectangularReflectionMapping: EquirectangularReflectionMapping, - EquirectangularRefractionMapping: EquirectangularRefractionMapping, - SphericalReflectionMapping: SphericalReflectionMapping, - CubeUVReflectionMapping: CubeUVReflectionMapping, - CubeUVRefractionMapping: CubeUVRefractionMapping, - RepeatWrapping: RepeatWrapping, - ClampToEdgeWrapping: ClampToEdgeWrapping, - MirroredRepeatWrapping: MirroredRepeatWrapping, - NearestFilter: NearestFilter, - NearestMipMapNearestFilter: NearestMipMapNearestFilter, - NearestMipMapLinearFilter: NearestMipMapLinearFilter, - LinearFilter: LinearFilter, - LinearMipMapNearestFilter: LinearMipMapNearestFilter, - LinearMipMapLinearFilter: LinearMipMapLinearFilter, - UnsignedByteType: UnsignedByteType, - ByteType: ByteType, - ShortType: ShortType, - UnsignedShortType: UnsignedShortType, - IntType: IntType, - UnsignedIntType: UnsignedIntType, - FloatType: FloatType, - HalfFloatType: HalfFloatType, - UnsignedShort4444Type: UnsignedShort4444Type, - UnsignedShort5551Type: UnsignedShort5551Type, - UnsignedShort565Type: UnsignedShort565Type, - UnsignedInt248Type: UnsignedInt248Type, - AlphaFormat: AlphaFormat, - RGBFormat: RGBFormat, - RGBAFormat: RGBAFormat, - LuminanceFormat: LuminanceFormat, - LuminanceAlphaFormat: LuminanceAlphaFormat, - RGBEFormat: RGBEFormat, - DepthFormat: DepthFormat, - DepthStencilFormat: DepthStencilFormat, - RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, - RGBA_S3TC_DXT1_Format: RGBA_S3TC_DXT1_Format, - RGBA_S3TC_DXT3_Format: RGBA_S3TC_DXT3_Format, - RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, - RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, - RGB_PVRTC_2BPPV1_Format: RGB_PVRTC_2BPPV1_Format, - RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, - RGBA_PVRTC_2BPPV1_Format: RGBA_PVRTC_2BPPV1_Format, - RGB_ETC1_Format: RGB_ETC1_Format, - LoopOnce: LoopOnce, - LoopRepeat: LoopRepeat, - LoopPingPong: LoopPingPong, - InterpolateDiscrete: InterpolateDiscrete, - InterpolateLinear: InterpolateLinear, - InterpolateSmooth: InterpolateSmooth, - ZeroCurvatureEnding: ZeroCurvatureEnding, - ZeroSlopeEnding: ZeroSlopeEnding, - WrapAroundEnding: WrapAroundEnding, - TrianglesDrawMode: TrianglesDrawMode, - TriangleStripDrawMode: TriangleStripDrawMode, - TriangleFanDrawMode: TriangleFanDrawMode, - LinearEncoding: LinearEncoding, - sRGBEncoding: sRGBEncoding, - GammaEncoding: GammaEncoding, - RGBEEncoding: RGBEEncoding, - LogLuvEncoding: LogLuvEncoding, - RGBM7Encoding: RGBM7Encoding, - RGBM16Encoding: RGBM16Encoding, - RGBDEncoding: RGBDEncoding, - BasicDepthPacking: BasicDepthPacking, - RGBADepthPacking: RGBADepthPacking, - CubeGeometry: BoxGeometry, - Face4: Face4, - LineStrip: LineStrip, - LinePieces: LinePieces, - MeshFaceMaterial: MeshFaceMaterial, - MultiMaterial: MultiMaterial, - PointCloud: PointCloud, - Particle: Particle, - ParticleSystem: ParticleSystem, - PointCloudMaterial: PointCloudMaterial, - ParticleBasicMaterial: ParticleBasicMaterial, - ParticleSystemMaterial: ParticleSystemMaterial, - Vertex: Vertex, - DynamicBufferAttribute: DynamicBufferAttribute, - Int8Attribute: Int8Attribute, - Uint8Attribute: Uint8Attribute, - Uint8ClampedAttribute: Uint8ClampedAttribute, - Int16Attribute: Int16Attribute, - Uint16Attribute: Uint16Attribute, - Int32Attribute: Int32Attribute, - Uint32Attribute: Uint32Attribute, - Float32Attribute: Float32Attribute, - Float64Attribute: Float64Attribute, - ClosedSplineCurve3: ClosedSplineCurve3, - SplineCurve3: SplineCurve3, - Spline: Spline, - AxisHelper: AxisHelper, - BoundingBoxHelper: BoundingBoxHelper, - EdgesHelper: EdgesHelper, - WireframeHelper: WireframeHelper, - XHRLoader: XHRLoader, - BinaryTextureLoader: BinaryTextureLoader, - GeometryUtils: GeometryUtils, - ImageUtils: ImageUtils, - Projector: Projector, - CanvasRenderer: CanvasRenderer -}); - -var THREE$1 = Object.assign({}, THREE) - /** * @author Russell Toris - rctoris@wpi.edu * @author David Gossow - dgossow@willowgarage.com */ -var REVISION$1 = '0.18.0'; +var ROS3D = ROS3D || { + REVISION : '0.15.0' +}; // Marker types -var MARKER_ARROW = 0; -var MARKER_CUBE = 1; -var MARKER_SPHERE = 2; -var MARKER_CYLINDER = 3; -var MARKER_LINE_STRIP = 4; -var MARKER_LINE_LIST = 5; -var MARKER_CUBE_LIST = 6; -var MARKER_SPHERE_LIST = 7; -var MARKER_POINTS = 8; -var MARKER_TEXT_VIEW_FACING = 9; -var MARKER_MESH_RESOURCE = 10; -var MARKER_TRIANGLE_LIST = 11; +ROS3D.MARKER_ARROW = 0; +ROS3D.MARKER_CUBE = 1; +ROS3D.MARKER_SPHERE = 2; +ROS3D.MARKER_CYLINDER = 3; +ROS3D.MARKER_LINE_STRIP = 4; +ROS3D.MARKER_LINE_LIST = 5; +ROS3D.MARKER_CUBE_LIST = 6; +ROS3D.MARKER_SPHERE_LIST = 7; +ROS3D.MARKER_POINTS = 8; +ROS3D.MARKER_TEXT_VIEW_FACING = 9; +ROS3D.MARKER_MESH_RESOURCE = 10; +ROS3D.MARKER_TRIANGLE_LIST = 11; // Interactive marker feedback types -var INTERACTIVE_MARKER_KEEP_ALIVE = 0; -var INTERACTIVE_MARKER_POSE_UPDATE = 1; -var INTERACTIVE_MARKER_MENU_SELECT = 2; -var INTERACTIVE_MARKER_BUTTON_CLICK = 3; -var INTERACTIVE_MARKER_MOUSE_DOWN = 4; -var INTERACTIVE_MARKER_MOUSE_UP = 5; +ROS3D.INTERACTIVE_MARKER_KEEP_ALIVE = 0; +ROS3D.INTERACTIVE_MARKER_POSE_UPDATE = 1; +ROS3D.INTERACTIVE_MARKER_MENU_SELECT = 2; +ROS3D.INTERACTIVE_MARKER_BUTTON_CLICK = 3; +ROS3D.INTERACTIVE_MARKER_MOUSE_DOWN = 4; +ROS3D.INTERACTIVE_MARKER_MOUSE_UP = 5; // Interactive marker control types -var INTERACTIVE_MARKER_NONE = 0; -var INTERACTIVE_MARKER_MENU = 1; -var INTERACTIVE_MARKER_BUTTON = 2; -var INTERACTIVE_MARKER_MOVE_AXIS = 3; -var INTERACTIVE_MARKER_MOVE_PLANE = 4; -var INTERACTIVE_MARKER_ROTATE_AXIS = 5; -var INTERACTIVE_MARKER_MOVE_ROTATE = 6; +ROS3D.INTERACTIVE_MARKER_NONE = 0; +ROS3D.INTERACTIVE_MARKER_MENU = 1; +ROS3D.INTERACTIVE_MARKER_BUTTON = 2; +ROS3D.INTERACTIVE_MARKER_MOVE_AXIS = 3; +ROS3D.INTERACTIVE_MARKER_MOVE_PLANE = 4; +ROS3D.INTERACTIVE_MARKER_ROTATE_AXIS = 5; +ROS3D.INTERACTIVE_MARKER_MOVE_ROTATE = 6; // Interactive marker rotation behavior -var INTERACTIVE_MARKER_INHERIT = 0; -var INTERACTIVE_MARKER_FIXED = 1; -var INTERACTIVE_MARKER_VIEW_FACING = 2; +ROS3D.INTERACTIVE_MARKER_INHERIT = 0; +ROS3D.INTERACTIVE_MARKER_FIXED = 1; +ROS3D.INTERACTIVE_MARKER_VIEW_FACING = 2; + +// Collada loader types +ROS3D.COLLADA_LOADER = 1; +ROS3D.COLLADA_LOADER_2 = 2; + /** * Create a THREE material based on the given RGBA values. @@ -45094,25 +57,25 @@ var INTERACTIVE_MARKER_VIEW_FACING = 2; * @param a - the alpha value * @returns the THREE material */ -var makeColorMaterial = function(r, g, b, a) { - var color = new THREE$1.Color(); +ROS3D.makeColorMaterial = function(r, g, b, a) { + var color = new THREE.Color(); color.setRGB(r, g, b); if (a <= 0.99) { - return new THREE$1.MeshBasicMaterial({ + return new THREE.MeshBasicMaterial({ color : color.getHex(), opacity : a + 0.1, transparent : true, depthWrite : true, - blendSrc : THREE$1.SrcAlphaFactor, - blendDst : THREE$1.OneMinusSrcAlphaFactor, - blendEquation : THREE$1.ReverseSubtractEquation, - blending : THREE$1.NormalBlending + blendSrc : THREE.SrcAlphaFactor, + blendDst : THREE.OneMinusSrcAlphaFactor, + blendEquation : THREE.ReverseSubtractEquation, + blending : THREE.NormalBlending }); } else { - return new THREE$1.MeshPhongMaterial({ + return new THREE.MeshPhongMaterial({ color : color.getHex(), opacity : a, - blending : THREE$1.NormalBlending + blending : THREE.NormalBlending }); } }; @@ -45125,9 +88,9 @@ var makeColorMaterial = function(r, g, b, a) { * @param planeNormal - the normal of the plane * @returns the intersection point */ -var intersectPlane = function(mouseRay, planeOrigin, planeNormal) { - var vector = new THREE$1.Vector3(); - var intersectPoint = new THREE$1.Vector3(); +ROS3D.intersectPlane = function(mouseRay, planeOrigin, planeNormal) { + var vector = new THREE.Vector3(); + var intersectPoint = new THREE.Vector3(); vector.subVectors(planeOrigin, mouseRay.origin); var dot = mouseRay.direction.dot(planeNormal); @@ -45151,8 +114,8 @@ var intersectPlane = function(mouseRay, planeOrigin, planeNormal) { * @param mouseRay - the mouse ray * @param the closest point between the two rays */ -var findClosestPoint = function(targetRay, mouseRay) { - var v13 = new THREE$1.Vector3(); +ROS3D.findClosestPoint = function(targetRay, mouseRay) { + var v13 = new THREE.Vector3(); v13.subVectors(targetRay.origin, mouseRay.origin); var v43 = mouseRay.direction.clone(); var v21 = targetRay.direction.clone(); @@ -45181,5136 +144,1010 @@ var findClosestPoint = function(targetRay, mouseRay) { * @param mousePos - the mouse position * @returns the closest axis point */ -var closestAxisPoint = function(axisRay, camera, mousePos) { +ROS3D.closestAxisPoint = function(axisRay, camera, mousePos) { + var projector = new THREE.Projector(); + // project axis onto screen var o = axisRay.origin.clone(); - o.project(camera); + projector.projectVector(o, camera); var o2 = axisRay.direction.clone().add(axisRay.origin); - o2.project(camera); + projector.projectVector(o2, camera); // d is the axis vector in screen space (d = o2-o) var d = o2.clone().sub(o); // t is the 2d ray param of perpendicular projection of mousePos onto o - var tmp = new THREE$1.Vector2(); + var tmp = new THREE.Vector2(); // (t = (mousePos - o) * d / (d*d)) var t = tmp.subVectors(mousePos, o).dot(d) / d.dot(d); // mp is the final 2d-projected mouse pos (mp = o + d*t) - var mp = new THREE$1.Vector2(); + var mp = new THREE.Vector2(); mp.addVectors(o, d.clone().multiplyScalar(t)); // go back to 3d by shooting a ray - var vector = new THREE$1.Vector3(mp.x, mp.y, 0.5); - vector.unproject(camera); - var mpRay = new THREE$1.Ray(camera.position, vector.sub(camera.position).normalize()); + var vector = new THREE.Vector3(mp.x, mp.y, 0.5); + projector.unprojectVector(vector, camera); + var mpRay = new THREE.Ray(camera.position, vector.sub(camera.position).normalize()); - return findClosestPoint(axisRay, mpRay); + return ROS3D.findClosestPoint(axisRay, mpRay); }; /** * @author Julius Kammerl - jkammerl@willowgarage.com */ -class DepthCloud extends THREE$1.Object3D { - - /** - * The DepthCloud object. - * - * @constructor - * @param options - object with following keys: - * - * * url - the URL of the stream - * * streamType (optional) - the stream type: mjpeg or vp8 video (defaults to vp8) - * * f (optional) - the camera's focal length (defaults to standard Kinect calibration) - * * maxDepthPerTile (optional) - the factor with which we control the desired depth range (defaults to 1.0) - * * pointSize (optional) - point size (pixels) for rendered point cloud - * * width (optional) - width of the video stream - * * height (optional) - height of the video stream - * * whiteness (optional) - blends rgb values to white (0..100) - * * varianceThreshold (optional) - threshold for variance filter, used for compression artifact removal - */ - constructor(options) { - super(); - options = options || {}; - - this.url = options.url; - this.streamType = options.streamType || 'vp8'; - this.f = options.f || 526; - this.maxDepthPerTile = options.maxDepthPerTile || 1.0; - this.pointSize = options.pointSize || 3; - this.width = options.width || 1024; - this.height = options.height || 1024; - this.whiteness = options.whiteness || 0; - this.varianceThreshold = options.varianceThreshold || 0.000016667; - - this.isMjpeg = this.streamType.toLowerCase() === 'mjpeg'; - - this.video = document.createElement(this.isMjpeg ? 'img' : 'video'); - this.video.addEventListener(this.isMjpeg ? 'load' : 'loadedmetadata', this.metaLoaded.bind(this), false); - - if (!this.isMjpeg) { - this.video.loop = true; - } - - this.video.src = this.url; - this.video.crossOrigin = 'Anonymous'; - this.video.setAttribute('crossorigin', 'Anonymous'); - - // define custom shaders - this.vertex_shader = [ - 'uniform sampler2D map;', - '', - 'uniform float width;', - 'uniform float height;', - 'uniform float nearClipping, farClipping;', - '', - 'uniform float pointSize;', - 'uniform float zOffset;', - '', - 'uniform float focallength;', - 'uniform float maxDepthPerTile;', - '', - 'varying vec2 vUvP;', - 'varying vec2 colorP;', - '', - 'varying float depthVariance;', - 'varying float maskVal;', - '', - 'float sampleDepth(vec2 pos)', - ' {', - ' float depth;', - ' ', - ' vec2 vUv = vec2( pos.x / (width*2.0), pos.y / (height*2.0)+0.5 );', - ' vec2 vUv2 = vec2( pos.x / (width*2.0)+0.5, pos.y / (height*2.0)+0.5 );', - ' ', - ' vec4 depthColor = texture2D( map, vUv );', - ' ', - ' depth = ( depthColor.r + depthColor.g + depthColor.b ) / 3.0 ;', - ' ', - ' if (depth>0.99)', - ' {', - ' vec4 depthColor2 = texture2D( map, vUv2 );', - ' float depth2 = ( depthColor2.r + depthColor2.g + depthColor2.b ) / 3.0 ;', - ' depth = 0.99+depth2;', - ' }', - ' ', - ' return depth;', - ' }', - '', - 'float median(float a, float b, float c)', - ' {', - ' float r=a;', - ' ', - ' if ( (a0.5) || (vUvP.y<0.5) || (vUvP.y>0.0))', - ' {', - ' vec2 smp = decodeDepth(vec2(position.x, position.y));', - ' float depth = smp.x;', - ' depthVariance = smp.y;', - ' ', - ' float z = -depth;', - ' ', - ' pos = vec4(', - ' ( position.x / width - 0.5 ) * z * 0.5 * maxDepthPerTile * (1000.0/focallength) * -1.0,', - ' ( position.y / height - 0.5 ) * z * 0.5 * maxDepthPerTile * (1000.0/focallength),', - ' (- z + zOffset / 1000.0) * maxDepthPerTile,', - ' 1.0);', - ' ', - ' vec2 maskP = vec2( position.x / (width*2.0), position.y / (height*2.0) );', - ' vec4 maskColor = texture2D( map, maskP );', - ' maskVal = ( maskColor.r + maskColor.g + maskColor.b ) / 3.0 ;', - ' }', - ' ', - ' gl_PointSize = pointSize;', - ' gl_Position = projectionMatrix * modelViewMatrix * pos;', - ' ', - '}' - ].join('\n'); - - this.fragment_shader = [ - 'uniform sampler2D map;', - 'uniform float varianceThreshold;', - 'uniform float whiteness;', - '', - 'varying vec2 vUvP;', - 'varying vec2 colorP;', - '', - 'varying float depthVariance;', - 'varying float maskVal;', - '', - '', - 'void main() {', - ' ', - ' vec4 color;', - ' ', - ' if ( (depthVariance>varianceThreshold) || (maskVal>0.5) ||(vUvP.x<0.0)|| (vUvP.x>0.5) || (vUvP.y<0.5) || (vUvP.y>1.0))', - ' { ', - ' discard;', - ' }', - ' else ', - ' {', - ' color = texture2D( map, colorP );', - ' ', - ' float fader = whiteness /100.0;', - ' ', - ' color.r = color.r * (1.0-fader)+ fader;', - ' ', - ' color.g = color.g * (1.0-fader)+ fader;', - ' ', - ' color.b = color.b * (1.0-fader)+ fader;', - ' ', - ' color.a = 1.0;//smoothstep( 20000.0, -20000.0, gl_FragCoord.z / gl_FragCoord.w );', - ' }', - ' ', - ' gl_FragColor = vec4( color.r, color.g, color.b, color.a );', - ' ', - '}' - ].join('\n'); - }; - - /** - * Callback called when video metadata is ready - */ - metaLoaded() { - this.metaLoaded = true; - this.initStreamer(); - }; - - /** - * Callback called when video metadata is ready - */ - initStreamer() { - - if (this.metaLoaded) { - this.texture = new THREE$1.Texture(this.video); - this.geometry = new THREE$1.Geometry(); - - for (var i = 0, l = this.width * this.height; i < l; i++) { - - var vertex = new THREE$1.Vector3(); - vertex.x = (i % this.width); - vertex.y = Math.floor(i / this.width); - - this.geometry.vertices.push(vertex); - } - - this.material = new THREE$1.ShaderMaterial({ - uniforms : { - 'map' : { - type : 't', - value : this.texture - }, - 'width' : { - type : 'f', - value : this.width - }, - 'height' : { - type : 'f', - value : this.height - }, - 'focallength' : { - type : 'f', - value : this.f - }, - 'pointSize' : { - type : 'f', - value : this.pointSize - }, - 'zOffset' : { - type : 'f', - value : 0 - }, - 'whiteness' : { - type : 'f', - value : this.whiteness - }, - 'varianceThreshold' : { - type : 'f', - value : this.varianceThreshold - }, - 'maxDepthPerTile': { - type : 'f', - value : this.maxDepthPerTile - }, - }, - vertexShader : this.vertex_shader, - fragmentShader : this.fragment_shader - }); - - this.mesh = new THREE$1.ParticleSystem(this.geometry, this.material); - this.mesh.position.x = 0; - this.mesh.position.y = 0; - this.add(this.mesh); - - var that = this; - - setInterval(function() { - if (that.isMjpeg || that.video.readyState === that.video.HAVE_ENOUGH_DATA) { - that.texture.needsUpdate = true; - } - }, 1000 / 30); - } - }; - - /** - * Start video playback - */ - startStream() { - if (!this.isMjpeg) { - this.video.play(); - } - }; - - /** - * Stop video playback - */ - stopStream() { - if (!this.isMjpeg) { - this.video.pause(); - } - }; -} - /** - * @author David Gossow - dgossow@willowgarage.com + * The DepthCloud object. + * + * @constructor + * @param options - object with following keys: + * + * * url - the URL of the stream + * * f (optional) - the camera's focal length (defaults to standard Kinect calibration) + * * pointSize (optional) - point size (pixels) for rendered point cloud + * * width (optional) - width of the video stream + * * height (optional) - height of the video stream + * * whiteness (optional) - blends rgb values to white (0..100) + * * varianceThreshold (optional) - threshold for variance filter, used for compression artifact removal */ +ROS3D.DepthCloud = function(options) { + options = options || {}; + THREE.Object3D.call(this); -class Arrow extends THREE$1.Mesh { + this.url = options.url; + this.f = options.f || 526; + this.pointSize = options.pointSize || 3; + this.width = options.width || 1024; + this.height = options.height || 1024; + this.whiteness = options.whiteness || 0; + this.varianceThreshold = options.varianceThreshold || 0.000016667; - /** - * A Arrow is a THREE object that can be used to display an arrow model. - * - * @constructor - * @param options - object with following keys: - * - * * origin (optional) - the origin of the arrow - * * direction (optional) - the direction vector of the arrow - * * length (optional) - the length of the arrow - * * headLength (optional) - the head length of the arrow - * * shaftDiameter (optional) - the shaft diameter of the arrow - * * headDiameter (optional) - the head diameter of the arrow - * * material (optional) - the material to use for this arrow - */ - constructor(options) { - options = options || {}; - var origin = options.origin || new THREE$1.Vector3(0, 0, 0); - var direction = options.direction || new THREE$1.Vector3(1, 0, 0); - var length = options.length || 1; - var headLength = options.headLength || 0.2; - var shaftDiameter = options.shaftDiameter || 0.05; - var headDiameter = options.headDiameter || 0.1; - var material = options.material || new THREE$1.MeshBasicMaterial(); + var metaLoaded = false; + this.video = document.createElement('video'); - var shaftLength = length - headLength; + this.video.addEventListener('loadedmetadata', this.metaLoaded.bind(this), false); - // create and merge geometry - var geometry = new THREE$1.CylinderGeometry(shaftDiameter * 0.5, shaftDiameter * 0.5, shaftLength, - 12, 1); - var m = new THREE$1.Matrix4(); - m.setPosition(new THREE$1.Vector3(0, shaftLength * 0.5, 0)); - geometry.applyMatrix(m); + this.video.loop = true; + this.video.src = this.url; + this.video.crossOrigin = 'Anonymous'; + this.video.setAttribute('crossorigin', 'Anonymous'); - // create the head - var coneGeometry = new THREE$1.CylinderGeometry(0, headDiameter * 0.5, headLength, 12, 1); - m.setPosition(new THREE$1.Vector3(0, shaftLength + (headLength * 0.5), 0)); - coneGeometry.applyMatrix(m); - - // put the arrow together - geometry.merge(coneGeometry); - - super(geometry, material); - - this.position.copy(origin); - this.setDirection(direction); - }; - - /** - * Set the direction of this arrow to that of the given vector. - * - * @param direction - the direction to set this arrow - */ - setDirection(direction) { - var axis = new THREE$1.Vector3(0, 1, 0).cross(direction); - var radians = Math.acos(new THREE$1.Vector3(0, 1, 0).dot(direction.clone().normalize())); - this.matrix = new THREE$1.Matrix4().makeRotationAxis(axis.normalize(), radians); - this.rotation.setFromRotationMatrix(this.matrix, this.rotation.order); - }; - - /** - * Set this arrow to be the given length. - * - * @param length - the new length of the arrow - */ - setLength(length) { - this.scale.set(length, length, length); - }; - - /** - * Set the color of this arrow to the given hex value. - * - * @param hex - the hex value of the color to use - */ - setColor(hex) { - this.material.color.setHex(hex); - }; - - /* - * Free memory of elements in this marker. - */ - dispose() { - if (this.geometry !== undefined) { - this.geometry.dispose(); - } - if (this.material !== undefined) { - this.material.dispose(); - } - }; -} - -/** - * @author aleeper / http://adamleeper.com/ - * @author mrdoob / http://mrdoob.com/ - * @author gero3 / https://github.com/gero3 - * @author Mugen87 / https://github.com/Mugen87 - * - * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. - * - * Supports both binary and ASCII encoded files, with automatic detection of type. - * - * The loader returns a non-indexed buffer geometry. - * - * Limitations: - * Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL). - * There is perhaps some question as to how valid it is to always assume little-endian-ness. - * ASCII decoding assumes file is UTF-8. - * - * Usage: - * var loader = new THREE.STLLoader(); - * loader.load( './models/stl/slotted_disk.stl', function ( geometry ) { - * scene.add( new THREE.Mesh( geometry ) ); - * }); - * - * For binary STLs geometry might contain colors for vertices. To use it: - * // use the same code to load STL as above - * if (geometry.hasColors) { - * material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: THREE.VertexColors }); - * } else { .... } - * var mesh = new THREE.Mesh( geometry, material ); - */ - -THREE$1.STLLoader = function (manager) { - - this.manager = (manager !== undefined) ? manager : THREE$1.DefaultLoadingManager; + // define custom shaders + this.vertex_shader = [ + 'uniform sampler2D map;', + '', + 'uniform float width;', + 'uniform float height;', + 'uniform float nearClipping, farClipping;', + '', + 'uniform float pointSize;', + 'uniform float zOffset;', + '', + 'uniform float focallength;', + '', + 'varying vec2 vUvP;', + 'varying vec2 colorP;', + '', + 'varying float depthVariance;', + 'varying float maskVal;', + '', + 'float sampleDepth(vec2 pos)', + ' {', + ' float depth;', + ' ', + ' vec2 vUv = vec2( pos.x / (width*2.0), pos.y / (height*2.0)+0.5 );', + ' vec2 vUv2 = vec2( pos.x / (width*2.0)+0.5, pos.y / (height*2.0)+0.5 );', + ' ', + ' vec4 depthColor = texture2D( map, vUv );', + ' ', + ' depth = ( depthColor.r + depthColor.g + depthColor.b ) / 3.0 ;', + ' ', + ' if (depth>0.99)', + ' {', + ' vec4 depthColor2 = texture2D( map, vUv2 );', + ' float depth2 = ( depthColor2.r + depthColor2.g + depthColor2.b ) / 3.0 ;', + ' depth = 0.99+depth2;', + ' }', + ' ', + ' return depth;', + ' }', + '', + 'float median(float a, float b, float c)', + ' {', + ' float r=a;', + ' ', + ' if ( (a0.5) || (vUvP.y<0.5) || (vUvP.y>0.0))', + ' {', + ' vec2 smp = decodeDepth(vec2(position.x, position.y));', + ' float depth = smp.x;', + ' depthVariance = smp.y;', + ' ', + ' float z = -depth;', + ' ', + ' pos = vec4(', + ' ( position.x / width - 0.5 ) * z * (1000.0/focallength) * -1.0,', + ' ( position.y / height - 0.5 ) * z * (1000.0/focallength),', + ' (- z + zOffset / 1000.0) * 2.0,', + ' 1.0);', + ' ', + ' vec2 maskP = vec2( position.x / (width*2.0), position.y / (height*2.0) );', + ' vec4 maskColor = texture2D( map, maskP );', + ' maskVal = ( maskColor.r + maskColor.g + maskColor.b ) / 3.0 ;', + ' }', + ' ', + ' gl_PointSize = pointSize;', + ' gl_Position = projectionMatrix * modelViewMatrix * pos;', + ' ', + '}' + ].join('\n'); + this.fragment_shader = [ + 'uniform sampler2D map;', + 'uniform float varianceThreshold;', + 'uniform float whiteness;', + '', + 'varying vec2 vUvP;', + 'varying vec2 colorP;', + '', + 'varying float depthVariance;', + 'varying float maskVal;', + '', + '', + 'void main() {', + ' ', + ' vec4 color;', + ' ', + ' if ( (depthVariance>varianceThreshold) || (maskVal>0.5) ||(vUvP.x<0.0)|| (vUvP.x>0.5) || (vUvP.y<0.5) || (vUvP.y>1.0))', + ' { ', + ' discard;', + ' }', + ' else ', + ' {', + ' color = texture2D( map, colorP );', + ' ', + ' float fader = whiteness /100.0;', + ' ', + ' color.r = color.r * (1.0-fader)+ fader;', + ' ', + ' color.g = color.g * (1.0-fader)+ fader;', + ' ', + ' color.b = color.b * (1.0-fader)+ fader;', + ' ', + ' color.a = 1.0;//smoothstep( 20000.0, -20000.0, gl_FragCoord.z / gl_FragCoord.w );', + ' }', + ' ', + ' gl_FragColor = vec4( color.r, color.g, color.b, color.a );', + ' ', + '}' + ].join('\n'); }; - -THREE$1.STLLoader.prototype = { - - constructor: THREE$1.STLLoader, - - load: function (url, onLoad, onProgress, onError) { - - var scope = this; - - var loader = new THREE$1.FileLoader(scope.manager); - loader.setResponseType('arraybuffer'); - loader.load(url, function (text) { - - onLoad(scope.parse(text)); - - }, onProgress, onError); - - }, - - parse: function (data) { - - function isBinary(data) { - - var expect, face_size, n_faces, reader; - reader = new DataView(data); - face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8); - n_faces = reader.getUint32(80, true); - expect = 80 + (32 / 8) + (n_faces * face_size); - - if (expect === reader.byteLength) { - - return true; - - } - - // An ASCII STL data must begin with 'solid ' as the first six bytes. - // However, ASCII STLs lacking the SPACE after the 'd' are known to be - // plentiful. So, check the first 5 bytes for 'solid'. - - // US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd' - - var solid = [115, 111, 108, 105, 100]; - - for (var i = 0; i < 5; i++) { - - // If solid[ i ] does not match the i-th byte, then it is not an - // ASCII STL; hence, it is binary and return true. - - if (solid[i] != reader.getUint8(i, false)) return true; - - } - - // First 5 bytes read "solid"; declare it to be an ASCII STL - - return false; - - } - - function parseBinary(data) { - - var reader = new DataView(data); - var faces = reader.getUint32(80, true); - - var r, g, b, hasColors = false, colors; - var defaultR, defaultG, defaultB, alpha; - - // process STL header - // check for default color in header ("COLOR=rgba" sequence). - - for (var index = 0; index < 80 - 10; index++) { - - if ((reader.getUint32(index, false) == 0x434F4C4F /*COLO*/) && - (reader.getUint8(index + 4) == 0x52 /*'R'*/) && - (reader.getUint8(index + 5) == 0x3D /*'='*/)) { - - hasColors = true; - colors = []; - - defaultR = reader.getUint8(index + 6) / 255; - defaultG = reader.getUint8(index + 7) / 255; - defaultB = reader.getUint8(index + 8) / 255; - alpha = reader.getUint8(index + 9) / 255; - - } - - } - - var dataOffset = 84; - var faceLength = 12 * 4 + 2; - - var geometry = new THREE$1.BufferGeometry(); - - var vertices = []; - var normals = []; - - for (var face = 0; face < faces; face++) { - - var start = dataOffset + face * faceLength; - var normalX = reader.getFloat32(start, true); - var normalY = reader.getFloat32(start + 4, true); - var normalZ = reader.getFloat32(start + 8, true); - - if (hasColors) { - - var packedColor = reader.getUint16(start + 48, true); - - if ((packedColor & 0x8000) === 0) { - - // facet has its own unique color - - r = (packedColor & 0x1F) / 31; - g = ((packedColor >> 5) & 0x1F) / 31; - b = ((packedColor >> 10) & 0x1F) / 31; - - } else { - - r = defaultR; - g = defaultG; - b = defaultB; - - } - - } - - for (var i = 1; i <= 3; i++) { - - var vertexstart = start + i * 12; - - vertices.push(reader.getFloat32(vertexstart, true)); - vertices.push(reader.getFloat32(vertexstart + 4, true)); - vertices.push(reader.getFloat32(vertexstart + 8, true)); - - normals.push(normalX, normalY, normalZ); - - if (hasColors) { - - colors.push(r, g, b); - - } - - } - - } - - geometry.addAttribute('position', new THREE$1.BufferAttribute(new Float32Array(vertices), 3)); - geometry.addAttribute('normal', new THREE$1.BufferAttribute(new Float32Array(normals), 3)); - - if (hasColors) { - - geometry.addAttribute('color', new THREE$1.BufferAttribute(new Float32Array(colors), 3)); - geometry.hasColors = true; - geometry.alpha = alpha; - - } - - return geometry; - - } - - function parseASCII(data) { - - var geometry = new THREE$1.BufferGeometry(); - var patternFace = /facet([\s\S]*?)endfacet/g; - var faceCounter = 0; - - var patternFloat = /[\s]+([+-]?(?:\d+.\d+|\d+.|\d+|.\d+)(?:[eE][+-]?\d+)?)/.source; - var patternVertex = new RegExp('vertex' + patternFloat + patternFloat + patternFloat, 'g'); - var patternNormal = new RegExp('normal' + patternFloat + patternFloat + patternFloat, 'g'); - - var vertices = []; - var normals = []; - - var normal = new THREE$1.Vector3(); - - var result; - - while ((result = patternFace.exec(data)) !== null) { - - var vertexCountPerFace = 0; - var normalCountPerFace = 0; - - var text = result[0]; - - while ((result = patternNormal.exec(text)) !== null) { - - normal.x = parseFloat(result[1]); - normal.y = parseFloat(result[2]); - normal.z = parseFloat(result[3]); - normalCountPerFace++; - - } - - while ((result = patternVertex.exec(text)) !== null) { - - vertices.push(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3])); - normals.push(normal.x, normal.y, normal.z); - vertexCountPerFace++; - - } - - // every face have to own ONE valid normal - - if (normalCountPerFace !== 1) { - - console.error('THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter); - - } - - // each face have to own THREE valid vertices - - if (vertexCountPerFace !== 3) { - - console.error('THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter); - - } - - faceCounter++; - - } - - geometry.addAttribute('position', new THREE$1.Float32BufferAttribute(vertices, 3)); - geometry.addAttribute('normal', new THREE$1.Float32BufferAttribute(normals, 3)); - - return geometry; - - } - - function ensureString(buffer) { - - if (typeof buffer !== 'string') { - - var array_buffer = new Uint8Array(buffer); - - if (window.TextDecoder !== undefined) { - - return new TextDecoder().decode(array_buffer); - - } - - var str = ''; - - for (var i = 0, il = buffer.byteLength; i < il; i++) { - - str += String.fromCharCode(array_buffer[i]); // implicitly assumes little-endian - - } - - return str; - - } else { - - return buffer; - - } - - } - - function ensureBinary(buffer) { - - if (typeof buffer === 'string') { - - var array_buffer = new Uint8Array(buffer.length); - for (var i = 0; i < buffer.length; i++) { - - array_buffer[i] = buffer.charCodeAt(i) & 0xff; // implicitly assumes little-endian - - } - return array_buffer.buffer || array_buffer; - - } else { - - return buffer; - - } - - } - - // start - - var binData = ensureBinary(data); - - return isBinary(binData) ? parseBinary(binData) : parseASCII(ensureString(data)); - - } - +ROS3D.DepthCloud.prototype.__proto__ = THREE.Object3D.prototype; + +/** + * Callback called when video metadata is ready + */ +ROS3D.DepthCloud.prototype.metaLoaded = function() { + this.metaLoaded = true; + this.initStreamer(); }; /** - * @author mrdoob / http://mrdoob.com/ - * @author Mugen87 / https://github.com/Mugen87 - * - * - * @Modified by Jihoon Lee from ColladerLoader.js@r88 - * To support rviz compatible collada viewing. - * See: #202 why it is forked. - * - * It is a fork from ColladerLoader.js in three.js. It follows three.js license. + * Callback called when video metadata is ready */ +ROS3D.DepthCloud.prototype.initStreamer = function() { -THREE$1.ColladaLoader = function (manager) { + if (this.metaLoaded) { + this.texture = new THREE.Texture(this.video); + this.geometry = new THREE.Geometry(); - this.manager = (manager !== undefined) ? manager : THREE$1.DefaultLoadingManager; + for (var i = 0, l = this.width * this.height; i < l; i++) { -}; - -THREE$1.ColladaLoader.prototype = { - - constructor: THREE$1.ColladaLoader, - - crossOrigin: 'Anonymous', - - load: function (url, onLoad, onProgress, onError) { - - var scope = this; - - var path = THREE$1.Loader.prototype.extractUrlBase(url); - - var loader = new THREE$1.FileLoader(scope.manager); - loader.load(url, function (text) { - - onLoad(scope.parse(text, path)); - - }, onProgress, onError); - - }, - - options: { - - set convertUpAxis(value) { - - console.warn('THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.'); + var vertex = new THREE.Vector3(); + vertex.x = (i % this.width); + vertex.y = Math.floor(i / this.width); + this.geometry.vertices.push(vertex); } - }, - - setCrossOrigin: function (value) { - - this.crossOrigin = value; - - }, - - parse: function (text, path) { - - function getElementsByTagName(xml, name) { - - // Non recursive xml.getElementsByTagName() ... - - var array = []; - var childNodes = xml.childNodes; - - for (var i = 0, l = childNodes.length; i < l; i++) { - - var child = childNodes[i]; - - if (child.nodeName === name) { - - array.push(child); - - } - - } - - return array; - - } - - function parseStrings(text) { - - if (text.length === 0) return []; - - var parts = text.trim().split(/\s+/); - var array = new Array(parts.length); - - for (var i = 0, l = parts.length; i < l; i++) { - - array[i] = parts[i]; - - } - - return array; - - } - - function parseFloats(text) { - - if (text.length === 0) return []; - - var parts = text.trim().split(/\s+/); - var array = new Array(parts.length); - - for (var i = 0, l = parts.length; i < l; i++) { - - array[i] = parseFloat(parts[i]); - - } - - return array; - - } - - function parseInts(text) { - - if (text.length === 0) return []; - - var parts = text.trim().split(/\s+/); - var array = new Array(parts.length); - - for (var i = 0, l = parts.length; i < l; i++) { - - array[i] = parseInt(parts[i]); - - } - - return array; - - } - - function parseId(text) { - - return text.substring(1); - - } - - function generateId() { - - return 'three_default_' + (count++); - - } - - function isEmpty(object) { - - return Object.keys(object).length === 0; - - } - - // asset - - function parseAsset(xml) { - - return { - unit: parseAssetUnit(getElementsByTagName(xml, 'unit')[0]), - upAxis: parseAssetUpAxis(getElementsByTagName(xml, 'up_axis')[0]) - }; - - } - - function parseAssetUnit(xml) { - - return xml !== undefined ? parseFloat(xml.getAttribute('meter')) : 1; - - } - - function parseAssetUpAxis(xml) { - - return xml !== undefined ? xml.textContent : 'Y_UP'; - - } - - // library - - function parseLibrary(xml, libraryName, nodeName, parser) { - - var library = getElementsByTagName(xml, libraryName)[0]; - - if (library !== undefined) { - - var elements = getElementsByTagName(library, nodeName); - - for (var i = 0; i < elements.length; i++) { - - parser(elements[i]); - - } - - } - - } - - function buildLibrary(data, builder) { - - for (var name in data) { - - var object = data[name]; - object.build = builder(data[name]); - - } - - } - - // get - - function getBuild(data, builder) { - - if (data.build !== undefined) return data.build; - - data.build = builder(data); - - return data.build; - - } - - // animation - - function parseAnimation(xml) { - - var data = { - sources: {}, - samplers: {}, - channels: {} - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - var id; - - switch (child.nodeName) { - - case 'source': - id = child.getAttribute('id'); - data.sources[id] = parseSource(child); - break; - - case 'sampler': - id = child.getAttribute('id'); - data.samplers[id] = parseAnimationSampler(child); - break; - - case 'channel': - id = child.getAttribute('target'); - data.channels[id] = parseAnimationChannel(child); - break; - - default: - console.log(child); - - } - - } - - library.animations[xml.getAttribute('id')] = data; - - } - - function parseAnimationSampler(xml) { - - var data = { - inputs: {}, - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'input': - var id = parseId(child.getAttribute('source')); - var semantic = child.getAttribute('semantic'); - data.inputs[semantic] = id; - break; - - } - - } - - return data; - - } - - function parseAnimationChannel(xml) { - - var data = {}; - - var target = xml.getAttribute('target'); - - // parsing SID Addressing Syntax - - var parts = target.split('/'); - - var id = parts.shift(); - var sid = parts.shift(); - - // check selection syntax - - var arraySyntax = (sid.indexOf('(') !== - 1); - var memberSyntax = (sid.indexOf('.') !== - 1); - - if (memberSyntax) { - - // member selection access - - parts = sid.split('.'); - sid = parts.shift(); - data.member = parts.shift(); - - } else if (arraySyntax) { - - // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices. - - var indices = sid.split('('); - sid = indices.shift(); - - for (var i = 0; i < indices.length; i++) { - - indices[i] = parseInt(indices[i].replace(/\)/, '')); - - } - - data.indices = indices; - - } - - data.id = id; - data.sid = sid; - - data.arraySyntax = arraySyntax; - data.memberSyntax = memberSyntax; - - data.sampler = parseId(xml.getAttribute('source')); - - return data; - - } - - function buildAnimation(data) { - - var tracks = []; - - var channels = data.channels; - var samplers = data.samplers; - var sources = data.sources; - - for (var target in channels) { - - if (channels.hasOwnProperty(target)) { - - var channel = channels[target]; - var sampler = samplers[channel.sampler]; - - var inputId = sampler.inputs.INPUT; - var outputId = sampler.inputs.OUTPUT; - - var inputSource = sources[inputId]; - var outputSource = sources[outputId]; - - var animation = buildAnimationChannel(channel, inputSource, outputSource); - - createKeyframeTracks(animation, tracks); - - } - - } - - return tracks; - - } - - function getAnimation(id) { - - return getBuild(library.animations[id], buildAnimation); - - } - - function buildAnimationChannel(channel, inputSource, outputSource) { - - var node = library.nodes[channel.id]; - var object3D = getNode(node.id); - - var transform = node.transforms[channel.sid]; - var defaultMatrix = node.matrix.clone().transpose(); - - var time, stride; - var i, il, j, jl; - - var data = {}; - - // the collada spec allows the animation of data in various ways. - // depending on the transform type (matrix, translate, rotate, scale), we execute different logic - - switch (transform) { - - case 'matrix': - - for (i = 0, il = inputSource.array.length; i < il; i++) { - - time = inputSource.array[i]; - stride = i * outputSource.stride; - - if (data[time] === undefined) data[time] = {}; - - if (channel.arraySyntax === true) { - - var value = outputSource.array[stride]; - var index = channel.indices[0] + 4 * channel.indices[1]; - - data[time][index] = value; - - } else { - - for (j = 0, jl = outputSource.stride; j < jl; j++) { - - data[time][j] = outputSource.array[stride + j]; - - } - - } - - } - - break; - - case 'translate': - console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); - break; - - case 'rotate': - console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); - break; - - case 'scale': - console.warn('THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform); - break; - - } - - var keyframes = prepareAnimationData(data, defaultMatrix); - - var animation = { - name: object3D.uuid, - keyframes: keyframes - }; - - return animation; - - } - - function prepareAnimationData(data, defaultMatrix) { - - var keyframes = []; - - // transfer data into a sortable array - - for (var time in data) { - - keyframes.push({ time: parseFloat(time), value: data[time] }); - - } - - // ensure keyframes are sorted by time - - keyframes.sort(ascending); - - // now we clean up all animation data, so we can use them for keyframe tracks - - for (var i = 0; i < 16; i++) { - - transformAnimationData(keyframes, i, defaultMatrix.elements[i]); - - } - - return keyframes; - - // array sort function - - function ascending(a, b) { - - return a.time - b.time; - - } - - } - - var position = new THREE$1.Vector3(); - var scale = new THREE$1.Vector3(); - var quaternion = new THREE$1.Quaternion(); - - function createKeyframeTracks(animation, tracks) { - - var keyframes = animation.keyframes; - var name = animation.name; - - var times = []; - var positionData = []; - var quaternionData = []; - var scaleData = []; - - for (var i = 0, l = keyframes.length; i < l; i++) { - - var keyframe = keyframes[i]; - - var time = keyframe.time; - var value = keyframe.value; - - matrix.fromArray(value).transpose(); - matrix.decompose(position, quaternion, scale); - - times.push(time); - positionData.push(position.x, position.y, position.z); - quaternionData.push(quaternion.x, quaternion.y, quaternion.z, quaternion.w); - scaleData.push(scale.x, scale.y, scale.z); - - } - - if (positionData.length > 0) tracks.push(new THREE$1.VectorKeyframeTrack(name + '.position', times, positionData)); - if (quaternionData.length > 0) tracks.push(new THREE$1.QuaternionKeyframeTrack(name + '.quaternion', times, quaternionData)); - if (scaleData.length > 0) tracks.push(new THREE$1.VectorKeyframeTrack(name + '.scale', times, scaleData)); - - return tracks; - - } - - function transformAnimationData(keyframes, property, defaultValue) { - - var keyframe; - - var empty = true; - var i, l; - - // check, if values of a property are missing in our keyframes - - for (i = 0, l = keyframes.length; i < l; i++) { - - keyframe = keyframes[i]; - - if (keyframe.value[property] === undefined) { - - keyframe.value[property] = null; // mark as missing - - } else { - - empty = false; - - } - - } - - if (empty === true) { - - // no values at all, so we set a default value - - for (i = 0, l = keyframes.length; i < l; i++) { - - keyframe = keyframes[i]; - - keyframe.value[property] = defaultValue; - - } - - } else { - - // filling gaps - - createMissingKeyframes(keyframes, property); - - } - - } - - function createMissingKeyframes(keyframes, property) { - - var prev, next; - - for (var i = 0, l = keyframes.length; i < l; i++) { - - var keyframe = keyframes[i]; - - if (keyframe.value[property] === null) { - - prev = getPrev(keyframes, i, property); - next = getNext(keyframes, i, property); - - if (prev === null) { - - keyframe.value[property] = next.value[property]; - continue; - - } - - if (next === null) { - - keyframe.value[property] = prev.value[property]; - continue; - - } - - interpolate(keyframe, prev, next, property); - - } - - } - - } - - function getPrev(keyframes, i, property) { - - while (i >= 0) { - - var keyframe = keyframes[i]; - - if (keyframe.value[property] !== null) return keyframe; - - i--; - - } - - return null; - - } - - function getNext(keyframes, i, property) { - - while (i < keyframes.length) { - - var keyframe = keyframes[i]; - - if (keyframe.value[property] !== null) return keyframe; - - i++; - - } - - return null; - - } - - function interpolate(key, prev, next, property) { - - if ((next.time - prev.time) === 0) { - - key.value[property] = prev.value[property]; - return; - - } - - key.value[property] = ((key.time - prev.time) * (next.value[property] - prev.value[property]) / (next.time - prev.time)) + prev.value[property]; - - } - - // animation clips - - function parseAnimationClip(xml) { - - var data = { - name: xml.getAttribute('id') || 'default', - start: parseFloat(xml.getAttribute('start') || 0), - end: parseFloat(xml.getAttribute('end') || 0), - animations: [] - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'instance_animation': - data.animations.push(parseId(child.getAttribute('url'))); - break; - - } - - } - - library.clips[xml.getAttribute('id')] = data; - - } - - function buildAnimationClip(data) { - - var tracks = []; - - var name = data.name; - var duration = (data.end - data.start) || - 1; - var animations = data.animations; - - for (var i = 0, il = animations.length; i < il; i++) { - - var animationTracks = getAnimation(animations[i]); - - for (var j = 0, jl = animationTracks.length; j < jl; j++) { - - tracks.push(animationTracks[j]); - - } - - } - - return new THREE$1.AnimationClip(name, duration, tracks); - - } - - function getAnimationClip(id) { - - return getBuild(library.clips[id], buildAnimationClip); - - } - - // controller - - function parseController(xml) { - - var data = {}; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'skin': - // there is exactly one skin per controller - data.id = parseId(child.getAttribute('source')); - data.skin = parseSkin(child); - break; - - case 'morph': - data.id = parseId(child.getAttribute('source')); - console.warn('THREE.ColladaLoader: Morph target animation not supported yet.'); - break; - - } - - } - - library.controllers[xml.getAttribute('id')] = data; - - } - - function parseSkin(xml) { - - var data = { - sources: {} - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'bind_shape_matrix': - data.bindShapeMatrix = parseFloats(child.textContent); - break; - - case 'source': - var id = child.getAttribute('id'); - data.sources[id] = parseSource(child); - break; - - case 'joints': - data.joints = parseJoints(child); - break; - - case 'vertex_weights': - data.vertexWeights = parseVertexWeights(child); - break; - - } - - } - - return data; - - } - - function parseJoints(xml) { - - var data = { - inputs: {} - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'input': - var semantic = child.getAttribute('semantic'); - var id = parseId(child.getAttribute('source')); - data.inputs[semantic] = id; - break; - - } - - } - - return data; - - } - - function parseVertexWeights(xml) { - - var data = { - inputs: {} - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'input': - var semantic = child.getAttribute('semantic'); - var id = parseId(child.getAttribute('source')); - var offset = parseInt(child.getAttribute('offset')); - data.inputs[semantic] = { id: id, offset: offset }; - break; - - case 'vcount': - data.vcount = parseInts(child.textContent); - break; - - case 'v': - data.v = parseInts(child.textContent); - break; - - } - - } - - return data; - - } - - function buildController(data) { - - var build = { - id: data.id - }; - - var geometry = library.geometries[build.id]; - - if (data.skin !== undefined) { - - build.skin = buildSkin(data.skin); - - // we enhance the 'sources' property of the corresponding geometry with our skin data - - geometry.sources.skinIndices = build.skin.indices; - geometry.sources.skinWeights = build.skin.weights; - - } - - return build; - - } - - function buildSkin(data) { - - var BONE_LIMIT = 4; - - var build = { - joints: [], // this must be an array to preserve the joint order - indices: { - array: [], - stride: BONE_LIMIT + this.material = new THREE.ShaderMaterial({ + uniforms : { + 'map' : { + type : 't', + value : this.texture }, - weights: { - array: [], - stride: BONE_LIMIT - } - }; - - var sources = data.sources; - var vertexWeights = data.vertexWeights; - - var vcount = vertexWeights.vcount; - var v = vertexWeights.v; - var jointOffset = vertexWeights.inputs.JOINT.offset; - var weightOffset = vertexWeights.inputs.WEIGHT.offset; - - var jointSource = data.sources[data.joints.inputs.JOINT]; - var inverseSource = data.sources[data.joints.inputs.INV_BIND_MATRIX]; - - var weights = sources[vertexWeights.inputs.WEIGHT.id].array; - var stride = 0; - - var i, j, l; - - // procces skin data for each vertex - - for (i = 0, l = vcount.length; i < l; i++) { - - var jointCount = vcount[i]; // this is the amount of joints that affect a single vertex - var vertexSkinData = []; - - for (j = 0; j < jointCount; j++) { - - var skinIndex = v[stride + jointOffset]; - var weightId = v[stride + weightOffset]; - var skinWeight = weights[weightId]; - - vertexSkinData.push({ index: skinIndex, weight: skinWeight }); - - stride += 2; - - } - - // we sort the joints in descending order based on the weights. - // this ensures, we only procced the most important joints of the vertex - - vertexSkinData.sort(descending); - - // now we provide for each vertex a set of four index and weight values. - // the order of the skin data matches the order of vertices - - for (j = 0; j < BONE_LIMIT; j++) { - - var d = vertexSkinData[j]; - - if (d !== undefined) { - - build.indices.array.push(d.index); - build.weights.array.push(d.weight); - - } else { - - build.indices.array.push(0); - build.weights.array.push(0); - - } - - } - - } - - // setup bind matrix - - build.bindMatrix = new THREE$1.Matrix4().fromArray(data.bindShapeMatrix).transpose(); - - // process bones and inverse bind matrix data - - for (i = 0, l = jointSource.array.length; i < l; i++) { - - var name = jointSource.array[i]; - var boneInverse = new THREE$1.Matrix4().fromArray(inverseSource.array, i * inverseSource.stride).transpose(); - - build.joints.push({ name: name, boneInverse: boneInverse }); - - } - - return build; - - // array sort function - - function descending(a, b) { - - return b.weight - a.weight; - - } - - } - - function getController(id) { - - return getBuild(library.controllers[id], buildController); - - } - - // image - - function parseImage(xml) { - - var data = { - init_from: getElementsByTagName(xml, 'init_from')[0].textContent - }; - - library.images[xml.getAttribute('id')] = data; - - } - - function buildImage(data) { - - if (data.build !== undefined) return data.build; - - return data.init_from; - - } - - function getImage(id) { - - return getBuild(library.images[id], buildImage); - - } - - // effect - - function parseEffect(xml) { - - var data = {}; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'profile_COMMON': - data.profile = parseEffectProfileCOMMON(child); - break; - - } - - } - - library.effects[xml.getAttribute('id')] = data; - - } - - function parseEffectProfileCOMMON(xml) { - - var data = { - surfaces: {}, - samplers: {} - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'newparam': - parseEffectNewparam(child, data); - break; - - case 'technique': - data.technique = parseEffectTechnique(child); - break; - - } - - } - - return data; - - } - - function parseEffectNewparam(xml, data) { - - var sid = xml.getAttribute('sid'); - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'surface': - data.surfaces[sid] = parseEffectSurface(child); - break; - - case 'sampler2D': - data.samplers[sid] = parseEffectSampler(child); - break; - - } - - } - - } - - function parseEffectSurface(xml) { - - var data = {}; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'init_from': - data.init_from = child.textContent; - break; - - } - - } - - return data; - - } - - function parseEffectSampler(xml) { - - var data = {}; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'source': - data.source = child.textContent; - break; - - } - - } - - return data; - - } - - function parseEffectTechnique(xml) { - - var data = {}; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'constant': - case 'lambert': - case 'blinn': - case 'phong': - data.type = child.nodeName; - data.parameters = parseEffectParameters(child); - break; - - } - - } - - return data; - - } - - function parseEffectParameters(xml) { - - var data = {}; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'emission': - case 'diffuse': - case 'specular': - case 'shininess': - case 'transparent': - case 'transparency': - data[child.nodeName] = parseEffectParameter(child); - break; - - } - - } - - return data; - - } - - function parseEffectParameter(xml) { - - var data = {}; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'color': - data[child.nodeName] = parseFloats(child.textContent); - break; - - case 'float': - data[child.nodeName] = parseFloat(child.textContent); - break; - - case 'texture': - data[child.nodeName] = { id: child.getAttribute('texture'), extra: parseEffectParameterTexture(child) }; - break; - - } - - } - - return data; - - } - - function parseEffectParameterTexture(xml) { - - var data = { - technique: {} - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'extra': - parseEffectParameterTextureExtra(child, data); - break; - - } - - } - - return data; - - } - - function parseEffectParameterTextureExtra(xml, data) { - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'technique': - parseEffectParameterTextureExtraTechnique(child, data); - break; - - } - - } - - } - - function parseEffectParameterTextureExtraTechnique(xml, data) { - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'repeatU': - case 'repeatV': - case 'offsetU': - case 'offsetV': - data.technique[child.nodeName] = parseFloat(child.textContent); - break; - - case 'wrapU': - case 'wrapV': - - // some files have values for wrapU/wrapV which become NaN via parseInt - - if (child.textContent.toUpperCase() === 'TRUE') { - - data.technique[child.nodeName] = 1; - - } else if (child.textContent.toUpperCase() === 'FALSE') { - - data.technique[child.nodeName] = 0; - - } else { - - data.technique[child.nodeName] = parseInt(child.textContent); - - } - - break; - - } - - } - - } - - function buildEffect(data) { - - return data; - - } - - function getEffect(id) { - - return getBuild(library.effects[id], buildEffect); - - } - - // material - - function parseMaterial(xml) { - - var data = { - name: xml.getAttribute('name') - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'instance_effect': - data.url = parseId(child.getAttribute('url')); - break; - - } - - } - - library.materials[xml.getAttribute('id')] = data; - - } - - function buildMaterial(data) { - - var effect = getEffect(data.url); - var technique = effect.profile.technique; - - var material; - - switch (technique.type) { - - case 'phong': - case 'blinn': - material = new THREE$1.MeshPhongMaterial(); - break; - - case 'lambert': - material = new THREE$1.MeshLambertMaterial(); - break; - - default: - material = new THREE$1.MeshBasicMaterial(); - break; - - } - - material.name = data.name; - - function getTexture(textureObject) { - - var sampler = effect.profile.samplers[textureObject.id]; - - if (sampler !== undefined) { - - var surface = effect.profile.surfaces[sampler.source]; - - var texture = textureLoader.load(getImage(surface.init_from)); - - var extra = textureObject.extra; - - if (extra !== undefined && extra.technique !== undefined && isEmpty(extra.technique) === false) { - - var technique = extra.technique; - - texture.wrapS = technique.wrapU ? THREE$1.RepeatWrapping : THREE$1.ClampToEdgeWrapping; - texture.wrapT = technique.wrapV ? THREE$1.RepeatWrapping : THREE$1.ClampToEdgeWrapping; - - texture.offset.set(technique.offsetU || 0, technique.offsetV || 0); - texture.repeat.set(technique.repeatU || 1, technique.repeatV || 1); - - } else { - - texture.wrapS = THREE$1.RepeatWrapping; - texture.wrapT = THREE$1.RepeatWrapping; - - } - - return texture; - - } - - console.error('THREE.ColladaLoader: Undefined sampler', textureObject.id); - - return null; - - } - - var parameters = technique.parameters; - - for (var key in parameters) { - - var parameter = parameters[key]; - - switch (key) { - - case 'diffuse': - if (parameter.color) material.color.fromArray(parameter.color); - if (parameter.texture) material.map = getTexture(parameter.texture); - break; - case 'specular': - if (parameter.color && material.specular) material.specular.fromArray(parameter.color); - if (parameter.texture) material.specularMap = getTexture(parameter.texture); - break; - case 'shininess': - if (parameter.float && material.shininess) - material.shininess = parameter.float; - break; - case 'emission': - if (parameter.color && material.emissive) - material.emissive.fromArray(parameter.color); - break; - case 'transparent': - // if ( parameter.texture ) material.alphaMap = getTexture( parameter.texture ); - material.transparent = true; - break; - case 'transparency': - if (parameter.float !== undefined) material.opacity = parameter.float; - material.transparent = true; - break; - - } - - } - - return material; - - } - - function getMaterial(id) { - - return getBuild(library.materials[id], buildMaterial); - - } - - // camera - - function parseCamera(xml) { - - var data = { - name: xml.getAttribute('name') - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'optics': - data.optics = parseCameraOptics(child); - break; - - } - - } - - library.cameras[xml.getAttribute('id')] = data; - - } - - function parseCameraOptics(xml) { - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - switch (child.nodeName) { - - case 'technique_common': - return parseCameraTechnique(child); - - } - - } - - return {}; - - } - - function parseCameraTechnique(xml) { - - var data = {}; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - switch (child.nodeName) { - - case 'perspective': - case 'orthographic': - - data.technique = child.nodeName; - data.parameters = parseCameraParameters(child); - - break; - - } - - } - - return data; - - } - - function parseCameraParameters(xml) { - - var data = {}; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - switch (child.nodeName) { - - case 'xfov': - case 'yfov': - case 'xmag': - case 'ymag': - case 'znear': - case 'zfar': - case 'aspect_ratio': - data[child.nodeName] = parseFloat(child.textContent); - break; - - } - - } - - return data; - - } - - function buildCamera(data) { - - var camera; - - switch (data.optics.technique) { - - case 'perspective': - camera = new THREE$1.PerspectiveCamera( - data.optics.parameters.yfov, - data.optics.parameters.aspect_ratio, - data.optics.parameters.znear, - data.optics.parameters.zfar - ); - break; - - case 'orthographic': - var ymag = data.optics.parameters.ymag; - var xmag = data.optics.parameters.xmag; - var aspectRatio = data.optics.parameters.aspect_ratio; - - xmag = (xmag === undefined) ? (ymag * aspectRatio) : xmag; - ymag = (ymag === undefined) ? (xmag / aspectRatio) : ymag; - - xmag *= 0.5; - ymag *= 0.5; - - camera = new THREE$1.OrthographicCamera( - - xmag, xmag, ymag, - ymag, // left, right, top, bottom - data.optics.parameters.znear, - data.optics.parameters.zfar - ); - break; - - default: - camera = new THREE$1.PerspectiveCamera(); - break; - - } - - camera.name = data.name; - - return camera; - - } - - function getCamera(id) { - var data = library.cameras[id]; - if (data !== undefined) { - return getBuild(data, buildCamera); - } - return null; - } - - // light - - function parseLight(xml) { - - var data = {}; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'technique_common': - data = parseLightTechnique(child); - break; - - } - - } - - library.lights[xml.getAttribute('id')] = data; - - } - - function parseLightTechnique(xml) { - - var data = {}; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'directional': - case 'point': - case 'spot': - case 'ambient': - - data.technique = child.nodeName; - data.parameters = parseLightParameters(child); - - } - - } - - return data; - - } - - function parseLightParameters(xml) { - - var data = {}; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'color': - var array = parseFloats(child.textContent); - data.color = new THREE$1.Color().fromArray(array); - break; - - case 'falloff_angle': - data.falloffAngle = parseFloat(child.textContent); - break; - - case 'quadratic_attenuation': - var f = parseFloat(child.textContent); - data.distance = f ? Math.sqrt(1 / f) : 0; - break; - - } - - } - - return data; - - } - - function buildLight(data) { - - var light; - - switch (data.technique) { - - case 'directional': - light = new THREE$1.DirectionalLight(); - break; - - case 'point': - light = new THREE$1.PointLight(); - break; - - case 'spot': - light = new THREE$1.SpotLight(); - break; - - case 'ambient': - light = new THREE$1.AmbientLight(); - break; - - } - - if (data.parameters.color) light.color.copy(data.parameters.color); - if (data.parameters.distance) light.distance = data.parameters.distance; - - return light; - - } - - // geometry - - function parseGeometry(xml) { - - var data = { - name: xml.getAttribute('name'), - sources: {}, - vertices: {}, - primitives: [] - }; - - var mesh = getElementsByTagName(xml, 'mesh')[0]; - - for (var i = 0; i < mesh.childNodes.length; i++) { - - var child = mesh.childNodes[i]; - - if (child.nodeType !== 1) continue; - - var id = child.getAttribute('id'); - - switch (child.nodeName) { - - case 'source': - data.sources[id] = parseSource(child); - break; - - case 'vertices': - // data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ]; - data.vertices = parseGeometryVertices(child); - break; - - case 'polygons': - console.warn('THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName); - break; - - case 'lines': - case 'linestrips': - case 'polylist': - case 'triangles': - data.primitives.push(parseGeometryPrimitive(child)); - break; - - default: - console.log(child); - - } - - } - - library.geometries[xml.getAttribute('id')] = data; - - } - - function parseSource(xml) { - - var data = { - array: [], - stride: 3 - }; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'float_array': - data.array = parseFloats(child.textContent); - break; - - case 'Name_array': - data.array = parseStrings(child.textContent); - break; - - case 'technique_common': - var accessor = getElementsByTagName(child, 'accessor')[0]; - - if (accessor !== undefined) { - - data.stride = parseInt(accessor.getAttribute('stride')); - - } - break; - - } - - } - - return data; - - } - - function parseGeometryVertices(xml) { - - var data = {}; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - data[child.getAttribute('semantic')] = parseId(child.getAttribute('source')); - - } - - return data; - - } - - function parseGeometryPrimitive(xml) { - - var primitive = { - type: xml.nodeName, - material: xml.getAttribute('material'), - count: parseInt(xml.getAttribute('count')), - inputs: {}, - stride: 0 - }; - - for (var i = 0, l = xml.childNodes.length; i < l; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'input': - var id = parseId(child.getAttribute('source')); - var semantic = child.getAttribute('semantic'); - var offset = parseInt(child.getAttribute('offset')); - primitive.inputs[semantic] = { id: id, offset: offset }; - primitive.stride = Math.max(primitive.stride, offset + 1); - break; - - case 'vcount': - primitive.vcount = parseInts(child.textContent); - break; - - case 'p': - primitive.p = parseInts(child.textContent); - break; - - } - - } - - return primitive; - - } - - function groupPrimitives(primitives) { - - var build = {}; - - for (var i = 0; i < primitives.length; i++) { - - var primitive = primitives[i]; - - if (build[primitive.type] === undefined) build[primitive.type] = []; - - build[primitive.type].push(primitive); - - } - - return build; - - } - - function buildGeometry(data) { - - var build = {}; - - var sources = data.sources; - var vertices = data.vertices; - var primitives = data.primitives; - - if (primitives.length === 0) return {}; - - // our goal is to create one buffer geoemtry for a single type of primitives - // first, we group all primitives by their type - - var groupedPrimitives = groupPrimitives(primitives); - - for (var type in groupedPrimitives) { - - // second, we create for each type of primitives (polylist,triangles or lines) a buffer geometry - - build[type] = buildGeometryType(groupedPrimitives[type], sources, vertices); - - } - - return build; - - } - - function buildGeometryType(primitives, sources, vertices) { - - var build = {}; - - var position = { array: [], stride: 0 }; - var normal = { array: [], stride: 0 }; - var uv = { array: [], stride: 0 }; - var color = { array: [], stride: 0 }; - - var skinIndex = { array: [], stride: 4 }; - var skinWeight = { array: [], stride: 4 }; - - var geometry = new THREE$1.BufferGeometry(); - - var materialKeys = []; - - var start = 0, count = 0; - - for (var p = 0; p < primitives.length; p++) { - - var primitive = primitives[p]; - var inputs = primitive.inputs; - var triangleCount = 1; - - if (primitive.vcount && primitive.vcount[0] === 4) { - - triangleCount = 2; // one quad -> two triangles - - } - - // groups - - if (primitive.type === 'lines' || primitive.type === 'linestrips') { - - count = primitive.count * 2; - - } else { - - count = primitive.count * 3 * triangleCount; - - } - - geometry.addGroup(start, count, p); - start += count; - - // material - - if (primitive.material) { - - materialKeys.push(primitive.material); - - } - - // geometry data - - for (var name in inputs) { - - var input = inputs[name]; - - switch (name) { - - case 'VERTEX': - for (var key in vertices) { - - var id = vertices[key]; - - switch (key) { - - case 'POSITION': - buildGeometryData(primitive, sources[id], input.offset, position.array); - position.stride = sources[id].stride; - - if (sources.skinWeights && sources.skinIndices) { - - buildGeometryData(primitive, sources.skinIndices, input.offset, skinIndex.array); - buildGeometryData(primitive, sources.skinWeights, input.offset, skinWeight.array); - - } - break; - - case 'NORMAL': - buildGeometryData(primitive, sources[id], input.offset, normal.array); - normal.stride = sources[id].stride; - break; - - case 'COLOR': - buildGeometryData(primitive, sources[id], input.offset, color.array); - color.stride = sources[id].stride; - break; - - case 'TEXCOORD': - buildGeometryData(primitive, sources[id], input.offset, uv.array); - uv.stride = sources[id].stride; - break; - - default: - console.warn('THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key); - - } - - } - break; - - case 'NORMAL': - buildGeometryData(primitive, sources[input.id], input.offset, normal.array); - normal.stride = sources[input.id].stride; - break; - - case 'COLOR': - buildGeometryData(primitive, sources[input.id], input.offset, color.array); - color.stride = sources[input.id].stride; - break; - - case 'TEXCOORD': - buildGeometryData(primitive, sources[input.id], input.offset, uv.array); - uv.stride = sources[input.id].stride; - break; - - } - - } - - } - - // build geometry - - if (position.array.length > 0) geometry.addAttribute('position', new THREE$1.Float32BufferAttribute(position.array, position.stride)); - if (normal.array.length > 0) geometry.addAttribute('normal', new THREE$1.Float32BufferAttribute(normal.array, normal.stride)); - if (color.array.length > 0) geometry.addAttribute('color', new THREE$1.Float32BufferAttribute(color.array, color.stride)); - if (uv.array.length > 0) geometry.addAttribute('uv', new THREE$1.Float32BufferAttribute(uv.array, uv.stride)); - - if (skinIndex.array.length > 0) geometry.addAttribute('skinIndex', new THREE$1.Float32BufferAttribute(skinIndex.array, skinIndex.stride)); - if (skinWeight.array.length > 0) geometry.addAttribute('skinWeight', new THREE$1.Float32BufferAttribute(skinWeight.array, skinWeight.stride)); - - build.data = geometry; - build.type = primitives[0].type; - build.materialKeys = materialKeys; - - return build; - - } - - function buildGeometryData(primitive, source, offset, array) { - - var indices = primitive.p; - var stride = primitive.stride; - var vcount = primitive.vcount; - - function pushVector(i) { - - var index = indices[i + offset] * sourceStride; - var length = index + sourceStride; - - for (; index < length; index++) { - - array.push(sourceArray[index]); - - } - - } - - var maxcount = 0; - - var sourceArray = source.array; - var sourceStride = source.stride; - - if (primitive.vcount !== undefined) { - - var index = 0; - - for (var i = 0, l = vcount.length; i < l; i++) { - - var count = vcount[i]; - - if (count === 4) { - - var a = index + stride * 0; - var b = index + stride * 1; - var c = index + stride * 2; - var d = index + stride * 3; - - pushVector(a); pushVector(b); pushVector(d); - pushVector(b); pushVector(c); pushVector(d); - - } else if (count === 3) { - - var a = index + stride * 0; - var b = index + stride * 1; - var c = index + stride * 2; - - pushVector(a); pushVector(b); pushVector(c); - - } else { - - maxcount = Math.max(maxcount, count); - - } - - index += stride * count; - - } - - if (maxcount > 0) { - - console.log('THREE.ColladaLoader: Geometry has faces with more than 4 vertices.'); - - } - - } else { - - for (var i = 0, l = indices.length; i < l; i += stride) { - - pushVector(i); - - } - - } - - } - - function getGeometry(id) { - - return getBuild(library.geometries[id], buildGeometry); - - } - - // kinematics - - function parseKinematicsModel(xml) { - - var data = { - name: xml.getAttribute('name') || '', - joints: {}, - links: [] - }; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'technique_common': - parseKinematicsTechniqueCommon(child, data); - break; - - } - - } - - library.kinematicsModels[xml.getAttribute('id')] = data; - - } - - function buildKinematicsModel(data) { - - if (data.build !== undefined) return data.build; - - return data; - - } - - function getKinematicsModel(id) { - - return getBuild(library.kinematicsModels[id], buildKinematicsModel); - - } - - function parseKinematicsTechniqueCommon(xml, data) { - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'joint': - data.joints[child.getAttribute('sid')] = parseKinematicsJoint(child); - break; - - case 'link': - data.links.push(parseKinematicsLink(child)); - break; - - } - - } - - } - - function parseKinematicsJoint(xml) { - - var data; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'prismatic': - case 'revolute': - data = parseKinematicsJointParameter(child); - break; - - } - - } - - return data; - - } - - function parseKinematicsJointParameter(xml, data) { - - var data = { - sid: xml.getAttribute('sid'), - name: xml.getAttribute('name') || '', - axis: new THREE$1.Vector3(), - limits: { - min: 0, - max: 0 + 'width' : { + type : 'f', + value : this.width }, - type: xml.nodeName, - static: false, - zeroPosition: 0, - middlePosition: 0 - }; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'axis': - var array = parseFloats(child.textContent); - data.axis.fromArray(array); - break; - case 'limits': - var max = child.getElementsByTagName('max')[0]; - var min = child.getElementsByTagName('min')[0]; - - data.limits.max = parseFloat(max.textContent); - data.limits.min = parseFloat(min.textContent); - break; - - } - - } - - // if min is equal to or greater than max, consider the joint static - - if (data.limits.min >= data.limits.max) { - - data.static = true; - - } - - // calculate middle position - - data.middlePosition = (data.limits.min + data.limits.max) / 2.0; - - return data; - - } - - function parseKinematicsLink(xml) { - - var data = { - sid: xml.getAttribute('sid'), - name: xml.getAttribute('name') || '', - attachments: [], - transforms: [] - }; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'attachment_full': - data.attachments.push(parseKinematicsAttachment(child)); - break; - - case 'matrix': - case 'translate': - case 'rotate': - data.transforms.push(parseKinematicsTransform(child)); - break; - - } - - } - - return data; - - } - - function parseKinematicsAttachment(xml) { - - var data = { - joint: xml.getAttribute('joint').split('/').pop(), - transforms: [], - links: [] - }; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'link': - data.links.push(parseKinematicsLink(child)); - break; - - case 'matrix': - case 'translate': - case 'rotate': - data.transforms.push(parseKinematicsTransform(child)); - break; - - } - - } - - return data; - - } - - function parseKinematicsTransform(xml) { - - var data = { - type: xml.nodeName - }; - - var array = parseFloats(xml.textContent); - - switch (data.type) { - - case 'matrix': - data.obj = new THREE$1.Matrix4(); - data.obj.fromArray(array).transpose(); - break; - - case 'translate': - data.obj = new THREE$1.Vector3(); - data.obj.fromArray(array); - break; - - case 'rotate': - data.obj = new THREE$1.Vector3(); - data.obj.fromArray(array); - data.angle = THREE$1.Math.degToRad(array[3]); - break; - - } - - return data; - - } - - function parseKinematicsScene(xml) { - - var data = { - bindJointAxis: [] - }; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'bind_joint_axis': - data.bindJointAxis.push(parseKinematicsBindJointAxis(child)); - break; - - } - - } - - library.kinematicsScenes[parseId(xml.getAttribute('url'))] = data; - - } - - function parseKinematicsBindJointAxis(xml) { - - var data = { - target: xml.getAttribute('target').split('/').pop() - }; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'axis': - var param = child.getElementsByTagName('param')[0]; - data.axis = param.textContent; - var tmpJointIndex = data.axis.split('inst_').pop().split('axis')[0]; - data.jointIndex = tmpJointIndex.substr(0, tmpJointIndex.length - 1); - break; - - } - - } - - return data; - - } - - function buildKinematicsScene(data) { - - if (data.build !== undefined) return data.build; - - return data; - - } - - function getKinematicsScene(id) { - - return getBuild(library.kinematicsScenes[id], buildKinematicsScene); - - } - - function setupKinematics() { - - var kinematicsModelId = Object.keys(library.kinematicsModels)[0]; - var kinematicsSceneId = Object.keys(library.kinematicsScenes)[0]; - var visualSceneId = Object.keys(library.visualScenes)[0]; - - if (kinematicsModelId === undefined || kinematicsSceneId === undefined) return; - - var kinematicsModel = getKinematicsModel(kinematicsModelId); - var kinematicsScene = getKinematicsScene(kinematicsSceneId); - var visualScene = getVisualScene(visualSceneId); - - var bindJointAxis = kinematicsScene.bindJointAxis; - var jointMap = {}; - - for (var i = 0, l = bindJointAxis.length; i < l; i++) { - - var axis = bindJointAxis[i]; - - // the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix' - - var targetElement = collada.querySelector('[sid="' + axis.target + '"]'); - - if (targetElement) { - - // get the parent of the transfrom element - - var parentVisualElement = targetElement.parentElement; - - // connect the joint of the kinematics model with the element in the visual scene - - connect(axis.jointIndex, parentVisualElement); - - } - - } - - function connect(jointIndex, visualElement) { - - var visualElementName = visualElement.getAttribute('name'); - var joint = kinematicsModel.joints[jointIndex]; - - visualScene.traverse(function (object) { - - if (object.name === visualElementName) { - - jointMap[jointIndex] = { - object: object, - transforms: buildTransformList(visualElement), - joint: joint, - position: joint.zeroPosition - }; - - } - - }); - - } - - var m0 = new THREE$1.Matrix4(); - - kinematics = { - - joints: kinematicsModel && kinematicsModel.joints, - - getJointValue: function (jointIndex) { - - var jointData = jointMap[jointIndex]; - - if (jointData) { - - return jointData.position; - - } else { - - console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.'); - - } - + 'height' : { + type : 'f', + value : this.height }, - - setJointValue: function (jointIndex, value) { - - var jointData = jointMap[jointIndex]; - - if (jointData) { - - var joint = jointData.joint; - - if (value > joint.limits.max || value < joint.limits.min) { - - console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').'); - - } else if (joint.static) { - - console.warn('THREE.ColladaLoader: Joint ' + jointIndex + ' is static.'); - - } else { - - var object = jointData.object; - var axis = joint.axis; - var transforms = jointData.transforms; - - matrix.identity(); - - // each update, we have to apply all transforms in the correct order - - for (var i = 0; i < transforms.length; i++) { - - var transform = transforms[i]; - - // if there is a connection of the transform node with a joint, apply the joint value - - if (transform.sid && transform.sid.indexOf(jointIndex) !== - 1) { - - switch (joint.type) { - - case 'revolute': - matrix.multiply(m0.makeRotationAxis(axis, THREE$1.Math.degToRad(value))); - break; - - case 'prismatic': - matrix.multiply(m0.makeTranslation(axis.x * value, axis.y * value, axis.z * value)); - break; - - default: - console.warn('THREE.ColladaLoader: Unknown joint type: ' + joint.type); - break; - - } - - } else { - - switch (transform.type) { - - case 'matrix': - matrix.multiply(transform.obj); - break; - - case 'translate': - matrix.multiply(m0.makeTranslation(transform.obj.x, transform.obj.y, transform.obj.z)); - break; - - case 'scale': - matrix.scale(transform.obj); - break; - - case 'rotate': - matrix.multiply(m0.makeRotationAxis(transform.obj, transform.angle)); - break; - - } - - } - - } - - object.matrix.copy(matrix); - object.matrix.decompose(object.position, object.quaternion, object.scale); - - jointMap[jointIndex].position = value; - - } - - } else { - - console.log('THREE.ColladaLoader: ' + jointIndex + ' does not exist.'); - - } - - } - - }; - - } - - function buildTransformList(node) { - - var transforms = []; - - var xml = collada.querySelector('[id="' + node.id + '"]'); - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'matrix': - var array = parseFloats(child.textContent); - var matrix = new THREE$1.Matrix4().fromArray(array).transpose(); - transforms.push({ - sid: child.getAttribute('sid'), - type: child.nodeName, - obj: matrix - }); - break; - - case 'translate': - case 'scale': - var array = parseFloats(child.textContent); - var vector = new THREE$1.Vector3().fromArray(array); - transforms.push({ - sid: child.getAttribute('sid'), - type: child.nodeName, - obj: vector - }); - break; - - case 'rotate': - var array = parseFloats(child.textContent); - var vector = new THREE$1.Vector3().fromArray(array); - var angle = THREE$1.Math.degToRad(array[3]); - transforms.push({ - sid: child.getAttribute('sid'), - type: child.nodeName, - obj: vector, - angle: angle - }); - break; - - } - - } - - return transforms; - - } - - // nodes - - function prepareNodes(xml) { - - var elements = xml.getElementsByTagName('node'); - - // ensure all node elements have id attributes - - for (var i = 0; i < elements.length; i++) { - - var element = elements[i]; - - if (element.hasAttribute('id') === false) { - - element.setAttribute('id', generateId()); - - } - - } - - } - - var matrix = new THREE$1.Matrix4(); - var vector = new THREE$1.Vector3(); - - function parseNode(xml) { - - var data = { - name: xml.getAttribute('name') || '', - type: xml.getAttribute('type'), - id: xml.getAttribute('id'), - sid: xml.getAttribute('sid'), - matrix: new THREE$1.Matrix4(), - nodes: [], - instanceCameras: [], - instanceControllers: [], - instanceLights: [], - instanceGeometries: [], - instanceNodes: [], - transforms: {} - }; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - if (child.nodeType !== 1) continue; - - switch (child.nodeName) { - - case 'node': - data.nodes.push(child.getAttribute('id')); - parseNode(child); - break; - - case 'instance_camera': - data.instanceCameras.push(parseId(child.getAttribute('url'))); - break; - - case 'instance_controller': - data.instanceControllers.push(parseNodeInstance(child)); - break; - - case 'instance_light': - data.instanceLights.push(parseId(child.getAttribute('url'))); - break; - - case 'instance_geometry': - data.instanceGeometries.push(parseNodeInstance(child)); - break; - - case 'instance_node': - data.instanceNodes.push(parseId(child.getAttribute('url'))); - break; - - case 'matrix': - var array = parseFloats(child.textContent); - data.matrix.multiply(matrix.fromArray(array).transpose()); - data.transforms[child.getAttribute('sid')] = child.nodeName; - break; - - case 'translate': - var array = parseFloats(child.textContent); - vector.fromArray(array); - data.matrix.multiply(matrix.makeTranslation(vector.x, vector.y, vector.z)); - data.transforms[child.getAttribute('sid')] = child.nodeName; - break; - - case 'rotate': - var array = parseFloats(child.textContent); - var angle = THREE$1.Math.degToRad(array[3]); - data.matrix.multiply(matrix.makeRotationAxis(vector.fromArray(array), angle)); - data.transforms[child.getAttribute('sid')] = child.nodeName; - break; - - case 'scale': - var array = parseFloats(child.textContent); - data.matrix.scale(vector.fromArray(array)); - data.transforms[child.getAttribute('sid')] = child.nodeName; - break; - - case 'extra': - break; - - default: - console.log(child); - - } - - } - - library.nodes[data.id] = data; - - return data; - - } - - function parseNodeInstance(xml) { - - var data = { - id: parseId(xml.getAttribute('url')), - materials: {}, - skeletons: [] - }; - - for (var i = 0; i < xml.childNodes.length; i++) { - - var child = xml.childNodes[i]; - - switch (child.nodeName) { - - case 'bind_material': - var instances = child.getElementsByTagName('instance_material'); - - for (var j = 0; j < instances.length; j++) { - - var instance = instances[j]; - var symbol = instance.getAttribute('symbol'); - var target = instance.getAttribute('target'); - - data.materials[symbol] = parseId(target); - - } - - break; - - case 'skeleton': - data.skeletons.push(parseId(child.textContent)); - break; - - default: - break; - - } - - } - - return data; - - } - - function buildSkeleton(skeletons, joints) { - - var boneData = []; - var sortedBoneData = []; - - var i, j, data; - - // a skeleton can have multiple root bones. collada expresses this - // situtation with multiple "skeleton" tags per controller instance - - for (i = 0; i < skeletons.length; i++) { - - var skeleton = skeletons[i]; - var root = getNode(skeleton); - - // setup bone data for a single bone hierarchy - - buildBoneHierarchy(root, joints, boneData); - - } - - // sort bone data (the order is defined in the corresponding controller) - - for (i = 0; i < joints.length; i++) { - - for (j = 0; j < boneData.length; j++) { - - data = boneData[j]; - - if (data.bone.name === joints[i].name) { - - sortedBoneData[i] = data; - data.processed = true; - break; - - } - - } - - } - - // add unprocessed bone data at the end of the list - - for (i = 0; i < boneData.length; i++) { - - data = boneData[i]; - - if (data.processed === false) { - - sortedBoneData.push(data); - data.processed = true; - - } - - } - - // setup arrays for skeleton creation - - var bones = []; - var boneInverses = []; - - for (i = 0; i < sortedBoneData.length; i++) { - - data = sortedBoneData[i]; - - bones.push(data.bone); - boneInverses.push(data.boneInverse); - - } - - return new THREE$1.Skeleton(bones, boneInverses); - - } - - function buildBoneHierarchy(root, joints, boneData) { - - // setup bone data from visual scene - - root.traverse(function (object) { - - if (object.isBone === true) { - - var boneInverse; - - // retrieve the boneInverse from the controller data - - for (var i = 0; i < joints.length; i++) { - - var joint = joints[i]; - - if (joint.name === object.name) { - - boneInverse = joint.boneInverse; - break; - - } - - } - - if (boneInverse === undefined) { - - // Unfortunately, there can be joints in the visual scene that are not part of the - // corresponding controller. In this case, we have to create a dummy boneInverse matrix - // for the respective bone. This bone won't affect any vertices, because there are no skin indices - // and weights defined for it. But we still have to add the bone to the sorted bone list in order to - // ensure a correct animation of the model. - - boneInverse = new THREE$1.Matrix4(); - - } - - boneData.push({ bone: object, boneInverse: boneInverse, processed: false }); - - } - - }); - - } - - function buildNode(data) { - - var objects = []; - - var matrix = data.matrix; - var nodes = data.nodes; - var type = data.type; - var instanceCameras = data.instanceCameras; - var instanceControllers = data.instanceControllers; - var instanceLights = data.instanceLights; - var instanceGeometries = data.instanceGeometries; - var instanceNodes = data.instanceNodes; - - // nodes - - for (var i = 0, l = nodes.length; i < l; i++) { - - objects.push(getNode(nodes[i])); - - } - - // instance cameras - - for (var i = 0, l = instanceCameras.length; i < l; i++) { - - var instanceCamera = getCamera(instanceCameras[i]); - - if (instanceCamera !== null) { - - objects.push(instanceCamera.clone()); - - } - - - } - - // instance controllers - - for (var i = 0, l = instanceControllers.length; i < l; i++) { - - var instance = instanceControllers[i]; - var controller = getController(instance.id); - var geometries = getGeometry(controller.id); - var newObjects = buildObjects(geometries, instance.materials); - - var skeletons = instance.skeletons; - var joints = controller.skin.joints; - - var skeleton = buildSkeleton(skeletons, joints); - - for (var j = 0, jl = newObjects.length; j < jl; j++) { - - var object = newObjects[j]; - - if (object.isSkinnedMesh) { - - object.bind(skeleton, controller.skin.bindMatrix); - object.normalizeSkinWeights(); - - } - - objects.push(object); - - } - - } - - // instance lights - - for (var i = 0, l = instanceLights.length; i < l; i++) { - var instanceCamera = getCamera(instanceCameras[i]); - - if (instanceCamera !== null) { - - objects.push(instanceCamera.clone()); - - } - - } - - // instance geometries - - for (var i = 0, l = instanceGeometries.length; i < l; i++) { - - var instance = instanceGeometries[i]; - - // a single geometry instance in collada can lead to multiple object3Ds. - // this is the case when primitives are combined like triangles and lines - - var geometries = getGeometry(instance.id); - var newObjects = buildObjects(geometries, instance.materials); - - for (var j = 0, jl = newObjects.length; j < jl; j++) { - - objects.push(newObjects[j]); - - } - - } - - // instance nodes - - for (var i = 0, l = instanceNodes.length; i < l; i++) { - - objects.push(getNode(instanceNodes[i]).clone()); - - } - - var object; - - if (nodes.length === 0 && objects.length === 1) { - - object = objects[0]; - - } else { - - object = (type === 'JOINT') ? new THREE$1.Bone() : new THREE$1.Group(); - - for (var i = 0; i < objects.length; i++) { - - object.add(objects[i]); - - } - - } - - object.name = (type === 'JOINT') ? data.sid : data.name; - object.matrix.copy(matrix); - object.matrix.decompose(object.position, object.quaternion, object.scale); - - return object; - - } - - function resolveMaterialBinding(keys, instanceMaterials) { - - var materials = []; - - for (var i = 0, l = keys.length; i < l; i++) { - - var id = instanceMaterials[keys[i]]; - materials.push(getMaterial(id)); - - } - - return materials; - - } - - function buildObjects(geometries, instanceMaterials) { - - var objects = []; - - for (var type in geometries) { - - var geometry = geometries[type]; - - var materials = resolveMaterialBinding(geometry.materialKeys, instanceMaterials); - - // handle case if no materials are defined - - if (materials.length === 0) { - - if (type === 'lines' || type === 'linestrips') { - - materials.push(new THREE$1.LineBasicMaterial()); - - } else { - - materials.push(new THREE$1.MeshPhongMaterial()); - - } - - } - - // regard skinning - - var skinning = (geometry.data.attributes.skinIndex !== undefined); - - if (skinning) { - - for (var i = 0, l = materials.length; i < l; i++) { - - materials[i].skinning = true; - - } - - } - - // choose between a single or multi materials (material array) - - var material = (materials.length === 1) ? materials[0] : materials; - - // now create a specific 3D object - - var object; - - switch (type) { - - case 'lines': - object = new THREE$1.LineSegments(geometry.data, material); - break; - - case 'linestrips': - object = new THREE$1.Line(geometry.data, material); - break; - - case 'triangles': - case 'polylist': - if (skinning) { - - object = new THREE$1.SkinnedMesh(geometry.data, material); - - } else { - - object = new THREE$1.Mesh(geometry.data, material); - - } - break; - - } - - objects.push(object); - - } - - return objects; - - } - - function getNode(id) { - - return getBuild(library.nodes[id], buildNode); - - } - - // visual scenes - - function parseVisualScene(xml) { - - var data = { - name: xml.getAttribute('name'), - children: [] - }; - - prepareNodes(xml); - - var elements = getElementsByTagName(xml, 'node'); - - for (var i = 0; i < elements.length; i++) { - - data.children.push(parseNode(elements[i])); - - } - - library.visualScenes[xml.getAttribute('id')] = data; - - } - - function buildVisualScene(data) { - - var group = new THREE$1.Group(); - group.name = data.name; - - var children = data.children; - - for (var i = 0; i < children.length; i++) { - - var child = children[i]; - - if (child.id === null) { - - group.add(buildNode(child)); - - } else { - - // if there is an ID, let's try to get the finished build (e.g. joints are already build) - - group.add(getNode(child.id)); - - } - - } - - return group; - - } - - function getVisualScene(id) { - - return getBuild(library.visualScenes[id], buildVisualScene); - - } - - // scenes - - function parseScene(xml) { - - var instance = getElementsByTagName(xml, 'instance_visual_scene')[0]; - return getVisualScene(parseId(instance.getAttribute('url'))); - - } - - function setupAnimations() { - - var clips = library.clips; - - if (isEmpty(clips) === true) { - - if (isEmpty(library.animations) === false) { - - // if there are animations but no clips, we create a default clip for playback - - var tracks = []; - - for (var id in library.animations) { - - var animationTracks = getAnimation(id); - - for (var i = 0, l = animationTracks.length; i < l; i++) { - - tracks.push(animationTracks[i]); - - } - - } - - animations.push(new THREE$1.AnimationClip('default', - 1, tracks)); - - } - - } else { - - for (var id in clips) { - - animations.push(getAnimationClip(id)); - - } - - } - - } - - console.time('THREE.ColladaLoader'); - - if (text.length === 0) { - - return { scene: new THREE$1.Scene() }; - - } - - console.time('THREE.ColladaLoader: DOMParser'); - - var xml = new DOMParser().parseFromString(text, 'application/xml'); - - console.timeEnd('THREE.ColladaLoader: DOMParser'); - - var collada = getElementsByTagName(xml, 'COLLADA')[0]; - - // metadata - - var version = collada.getAttribute('version'); - console.log('THREE.ColladaLoader: File version', version); - - var asset = parseAsset(getElementsByTagName(collada, 'asset')[0]); - var textureLoader = new THREE$1.TextureLoader(this.manager); - textureLoader.setPath(path).setCrossOrigin(this.crossOrigin); - - // - - var animations = []; - var kinematics = {}; - var count = 0; - - // - - var library = { - animations: {}, - clips: {}, - controllers: {}, - images: {}, - effects: {}, - materials: {}, - cameras: {}, - lights: {}, - geometries: {}, - nodes: {}, - visualScenes: {}, - kinematicsModels: {}, - kinematicsScenes: {} - }; - - console.time('THREE.ColladaLoader: Parse'); - - parseLibrary(collada, 'library_animations', 'animation', parseAnimation); - parseLibrary(collada, 'library_animation_clips', 'animation_clip', parseAnimationClip); - parseLibrary(collada, 'library_controllers', 'controller', parseController); - parseLibrary(collada, 'library_images', 'image', parseImage); - parseLibrary(collada, 'library_effects', 'effect', parseEffect); - parseLibrary(collada, 'library_materials', 'material', parseMaterial); - parseLibrary(collada, 'library_cameras', 'camera', parseCamera); - parseLibrary(collada, 'library_lights', 'light', parseLight); - parseLibrary(collada, 'library_geometries', 'geometry', parseGeometry); - parseLibrary(collada, 'library_nodes', 'node', parseNode); - parseLibrary(collada, 'library_visual_scenes', 'visual_scene', parseVisualScene); - parseLibrary(collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel); - parseLibrary(collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene); - - console.timeEnd('THREE.ColladaLoader: Parse'); - - console.time('THREE.ColladaLoader: Build'); - - buildLibrary(library.animations, buildAnimation); - buildLibrary(library.clips, buildAnimationClip); - buildLibrary(library.controllers, buildController); - buildLibrary(library.images, buildImage); - buildLibrary(library.effects, buildEffect); - buildLibrary(library.materials, buildMaterial); - buildLibrary(library.cameras, buildCamera); - buildLibrary(library.lights, buildLight); - buildLibrary(library.geometries, buildGeometry); - buildLibrary(library.visualScenes, buildVisualScene); - - console.timeEnd('THREE.ColladaLoader: Build'); - - setupAnimations(); - setupKinematics(); - - var scene = parseScene(getElementsByTagName(collada, 'scene')[0]); - - /* - * up_axis of some robot models in ROS world aren't properly set because - * rviz ignores this field. Thus, ignores Z_UP to show urdfs just like rviz. - * See https://github.com/ros-visualization/rviz/issues/1045 for the detail - if ( asset.upAxis === 'Z_UP' ) { - - scene.rotation.x = - Math.PI / 2; - - } - */ - - scene.scale.multiplyScalar(asset.unit); - - console.timeEnd('THREE.ColladaLoader'); - - return { - animations: animations, - kinematics: kinematics, - library: library, - scene: scene - }; - - } - -}; - -/** - * @author Jihoon Lee - jihoonlee.in@gmail.com - * @author Russell Toris - rctoris@wpi.edu - */ - -class MeshResource extends THREE$1.Object3D { - - /** - * A MeshResource is an THREE object that will load from a external mesh file. Currently loads - * Collada files. - * - * @constructor - * @param options - object with following keys: - * - * * path (optional) - the base path to the associated models that will be loaded - * * resource - the resource file name to load - * * material (optional) - the material to use for the object - * * warnings (optional) - if warnings should be printed - */ - constructor(options) { - super(); - var that = this; - options = options || {}; - var path = options.path || '/'; - var resource = options.resource; - var material = options.material || null; - this.warnings = options.warnings; - - - // check for a trailing '/' - if (path.substr(path.length - 1) !== '/') { - path += '/'; - } - - var uri = path + resource; - var fileType = uri.substr(-4).toLowerCase(); - - // check the type - var loader; - if (fileType === '.dae') { - loader = new THREE$1.ColladaLoader(); - loader.log = function(message) { - if (that.warnings) { - console.warn(message); - } - }; - loader.load( - uri, - function colladaReady(collada) { - // check for a scale factor in ColladaLoader2 - // add a texture to anything that is missing one - if(material !== null) { - collada.scene.traverse(function(child) { - if(child instanceof THREE$1.Mesh) { - if(child.material === undefined) { - child.material = material; - } - } - }); - } - - that.add(collada.scene); + 'focallength' : { + type : 'f', + value : this.f }, - /*onProgress=*/null, - function onLoadError(error) { - console.error(error); - }); - } else if (fileType === '.stl') { - loader = new THREE$1.STLLoader(); - { - loader.load(uri, - function ( geometry ) { - geometry.computeFaceNormals(); - var mesh; - if(material !== null) { - mesh = new THREE$1.Mesh( geometry, material ); - } else { - mesh = new THREE$1.Mesh( geometry, - new THREE$1.MeshBasicMaterial( { color: 0x999999 } ) ); - } - that.add(mesh); - }, - /*onProgress=*/null, - function onLoadError(error) { - console.error(error); - }); - } - } - }; -} - -/** - * @author David Gossow - dgossow@willowgarage.com - */ - -class TriangleList extends THREE$1.Object3D { - - /** - * A TriangleList is a THREE object that can be used to display a list of triangles as a geometry. - * - * @constructor - * @param options - object with following keys: - * - * * material (optional) - the material to use for the object - * * vertices - the array of vertices to use - * * colors - the associated array of colors to use - */ - constructor(options) { - options = options || {}; - var material = options.material || new THREE$1.MeshBasicMaterial(); - var vertices = options.vertices; - var colors = options.colors; - - super(); - - // set the material to be double sided - material.side = THREE$1.DoubleSide; - - // construct the geometry - var geometry = new THREE$1.Geometry(); - for (i = 0; i < vertices.length; i++) { - geometry.vertices.push(new THREE$1.Vector3(vertices[i].x, vertices[i].y, vertices[i].z)); - } - - // set the colors - var i, j; - if (colors.length === vertices.length) { - // use per-vertex color - for (i = 0; i < vertices.length; i += 3) { - var faceVert = new THREE$1.Face3(i, i + 1, i + 2); - for (j = i * 3; j < i * 3 + 3; i++) { - var color = new THREE$1.Color(); - color.setRGB(colors[i].r, colors[i].g, colors[i].b); - faceVert.vertexColors.push(color); + 'pointSize' : { + type : 'f', + value : this.pointSize + }, + 'zOffset' : { + type : 'f', + value : 0 + }, + 'whiteness' : { + type : 'f', + value : this.whiteness + }, + 'varianceThreshold' : { + type : 'f', + value : this.varianceThreshold } - geometry.faces.push(faceVert); - } - material.vertexColors = THREE$1.VertexColors; - } else if (colors.length === vertices.length / 3) { - // use per-triangle color - for (i = 0; i < vertices.length; i += 3) { - var faceTri = new THREE$1.Face3(i, i + 1, i + 2); - faceTri.color.setRGB(colors[i / 3].r, colors[i / 3].g, colors[i / 3].b); - geometry.faces.push(faceTri); - } - material.vertexColors = THREE$1.FaceColors; - } else { - // use marker color - for (i = 0; i < vertices.length; i += 3) { - var face = new THREE$1.Face3(i, i + 1, i + 2); - geometry.faces.push(face); - } - } - - geometry.computeBoundingBox(); - geometry.computeBoundingSphere(); - geometry.computeFaceNormals(); - - this.add(new THREE$1.Mesh(geometry, material)); - }; - - /** - * Set the color of this object to the given hex value. - * - * @param hex - the hex value of the color to set - */ - setColor(hex) { - this.mesh.material.color.setHex(hex); - }; -} - -/** - * @author David Gossow - dgossow@willowgarage.com - * @author Russell Toris - rctoris@wpi.edu - */ - -class Marker extends THREE$1.Object3D { - - /** - * A Marker can convert a ROS marker message into a THREE object. - * - * @constructor - * @param options - object with following keys: - * - * * path - the base path or URL for any mesh files that will be loaded for this marker - * * message - the marker message - */ - constructor(options) { - super(); - - options = options || {}; - var path = options.path || '/'; - var message = options.message; - - // check for a trailing '/' - if (path.substr(path.length - 1) !== '/') { - path += '/'; - } - - if(message.scale) { - this.msgScale = [message.scale.x, message.scale.y, message.scale.z]; - } - else { - this.msgScale = [1,1,1]; - } - this.msgColor = message.color; - this.msgMesh = undefined; - - // set the pose and get the color - this.setPose(message.pose); - var colorMaterial = makeColorMaterial(this.msgColor.r, - this.msgColor.g, this.msgColor.b, this.msgColor.a); - - // create the object based on the type - switch (message.type) { - case MARKER_ARROW: - // get the sizes for the arrow - var len = message.scale.x; - var headLength = len * 0.23; - var headDiameter = message.scale.y; - var shaftDiameter = headDiameter * 0.5; - - // determine the points - var direction, p1 = null; - if (message.points.length === 2) { - p1 = new THREE$1.Vector3(message.points[0].x, message.points[0].y, message.points[0].z); - var p2 = new THREE$1.Vector3(message.points[1].x, message.points[1].y, message.points[1].z); - direction = p1.clone().negate().add(p2); - // direction = p2 - p1; - len = direction.length(); - headDiameter = message.scale.y; - shaftDiameter = message.scale.x; - - if (message.scale.z !== 0.0) { - headLength = message.scale.z; - } - } - - // add the marker - this.add(new Arrow({ - direction : direction, - origin : p1, - length : len, - headLength : headLength, - shaftDiameter : shaftDiameter, - headDiameter : headDiameter, - material : colorMaterial - })); - break; - case MARKER_CUBE: - // set the cube dimensions - var cubeGeom = new THREE$1.BoxGeometry(message.scale.x, message.scale.y, message.scale.z); - this.add(new THREE$1.Mesh(cubeGeom, colorMaterial)); - break; - case MARKER_SPHERE: - // set the sphere dimensions - var sphereGeom = new THREE$1.SphereGeometry(0.5); - var sphereMesh = new THREE$1.Mesh(sphereGeom, colorMaterial); - sphereMesh.scale.x = message.scale.x; - sphereMesh.scale.y = message.scale.y; - sphereMesh.scale.z = message.scale.z; - this.add(sphereMesh); - break; - case MARKER_CYLINDER: - // set the cylinder dimensions - var cylinderGeom = new THREE$1.CylinderGeometry(0.5, 0.5, 1, 16, 1, false); - var cylinderMesh = new THREE$1.Mesh(cylinderGeom, colorMaterial); - cylinderMesh.quaternion.setFromAxisAngle(new THREE$1.Vector3(1, 0, 0), Math.PI * 0.5); - cylinderMesh.scale.set(message.scale.x, message.scale.z, message.scale.y); - this.add(cylinderMesh); - break; - case MARKER_LINE_STRIP: - var lineStripGeom = new THREE$1.Geometry(); - var lineStripMaterial = new THREE$1.LineBasicMaterial({ - size : message.scale.x - }); - - // add the points - var j; - for ( j = 0; j < message.points.length; j++) { - var pt = new THREE$1.Vector3(); - pt.x = message.points[j].x; - pt.y = message.points[j].y; - pt.z = message.points[j].z; - lineStripGeom.vertices.push(pt); - } - - // determine the colors for each - if (message.colors.length === message.points.length) { - lineStripMaterial.vertexColors = true; - for ( j = 0; j < message.points.length; j++) { - var clr = new THREE$1.Color(); - clr.setRGB(message.colors[j].r, message.colors[j].g, message.colors[j].b); - lineStripGeom.colors.push(clr); - } - } else { - lineStripMaterial.color.setRGB(message.color.r, message.color.g, message.color.b); - } - - // add the line - this.add(new THREE$1.Line(lineStripGeom, lineStripMaterial)); - break; - case MARKER_LINE_LIST: - var lineListGeom = new THREE$1.Geometry(); - var lineListMaterial = new THREE$1.LineBasicMaterial({ - size : message.scale.x - }); - - // add the points - var k; - for ( k = 0; k < message.points.length; k++) { - var v = new THREE$1.Vector3(); - v.x = message.points[k].x; - v.y = message.points[k].y; - v.z = message.points[k].z; - lineListGeom.vertices.push(v); - } - - // determine the colors for each - if (message.colors.length === message.points.length) { - lineListMaterial.vertexColors = true; - for ( k = 0; k < message.points.length; k++) { - var c = new THREE$1.Color(); - c.setRGB(message.colors[k].r, message.colors[k].g, message.colors[k].b); - lineListGeom.colors.push(c); - } - } else { - lineListMaterial.color.setRGB(message.color.r, message.color.g, message.color.b); - } - - // add the line - this.add(new THREE$1.Line(lineListGeom, lineListMaterial,THREE$1.LinePieces)); - break; - case MARKER_CUBE_LIST: - // holds the main object - var object = new THREE$1.Object3D(); - - // check if custom colors should be used - var numPoints = message.points.length; - var createColors = (numPoints === message.colors.length); - // do not render giant lists - var stepSize = Math.ceil(numPoints / 1250); - - // add the points - var p, cube, curColor, newMesh; - for (p = 0; p < numPoints; p+=stepSize) { - cube = new THREE$1.BoxGeometry(message.scale.x, message.scale.y, message.scale.z); - - // check the color - if(createColors) { - curColor = makeColorMaterial(message.colors[p].r, message.colors[p].g, message.colors[p].b, message.colors[p].a); - } else { - curColor = colorMaterial; - } - - newMesh = new THREE$1.Mesh(cube, curColor); - newMesh.position.x = message.points[p].x; - newMesh.position.y = message.points[p].y; - newMesh.position.z = message.points[p].z; - object.add(newMesh); - } - - this.add(object); - break; - case MARKER_SPHERE_LIST: - // holds the main object - var sphereObject = new THREE$1.Object3D(); - - // check if custom colors should be used - var numSpherePoints = message.points.length; - var createSphereColors = (numSpherePoints === message.colors.length); - // do not render giant lists - var sphereStepSize = Math.ceil(numSpherePoints / 1250); - - // add the points - var q, sphere, curSphereColor, newSphereMesh; - for (q = 0; q < numSpherePoints; q+=sphereStepSize) { - sphere = new THREE$1.SphereGeometry(0.5, 8, 8); - - // check the color - if(createSphereColors) { - curSphereColor = makeColorMaterial(message.colors[q].r, message.colors[q].g, message.colors[q].b, message.colors[q].a); - } else { - curSphereColor = colorMaterial; - } - - newSphereMesh = new THREE$1.Mesh(sphere, curSphereColor); - newSphereMesh.scale.x = message.scale.x; - newSphereMesh.scale.y = message.scale.y; - newSphereMesh.scale.z = message.scale.z; - newSphereMesh.position.x = message.points[q].x; - newSphereMesh.position.y = message.points[q].y; - newSphereMesh.position.z = message.points[q].z; - sphereObject.add(newSphereMesh); - } - this.add(sphereObject); - break; - case MARKER_POINTS: - // for now, use a particle system for the lists - var geometry = new THREE$1.Geometry(); - var material = new THREE$1.ParticleBasicMaterial({ - size : message.scale.x - }); - - // add the points - var i; - for ( i = 0; i < message.points.length; i++) { - var vertex = new THREE$1.Vector3(); - vertex.x = message.points[i].x; - vertex.y = message.points[i].y; - vertex.z = message.points[i].z; - geometry.vertices.push(vertex); - } - - // determine the colors for each - if (message.colors.length === message.points.length) { - material.vertexColors = true; - for ( i = 0; i < message.points.length; i++) { - var color = new THREE$1.Color(); - color.setRGB(message.colors[i].r, message.colors[i].g, message.colors[i].b); - geometry.colors.push(color); - } - } else { - material.color.setRGB(message.color.r, message.color.g, message.color.b); - } - - // add the particle system - this.add(new THREE$1.ParticleSystem(geometry, material)); - break; - case MARKER_TEXT_VIEW_FACING: - // only work on non-empty text - if (message.text.length > 0) { - // Use a THREE.Sprite to always be view-facing - // ( code from http://stackoverflow.com/a/27348780 ) - var textColor = this.msgColor; - - var canvas = document.createElement('canvas'); - var context = canvas.getContext('2d'); - var textHeight = 100; - var fontString = 'normal ' + textHeight + 'px sans-serif'; - context.font = fontString; - var metrics = context.measureText( message.text ); - var textWidth = metrics.width; - - canvas.width = textWidth; - // To account for overhang (like the letter 'g'), make the canvas bigger - // The non-text portion is transparent anyway - canvas.height = 1.5 * textHeight; - - // this does need to be set again - context.font = fontString; - context.fillStyle = 'rgba(' - + Math.round(255 * textColor.r) + ', ' - + Math.round(255 * textColor.g) + ', ' - + Math.round(255 * textColor.b) + ', ' - + textColor.a + ')'; - context.textAlign = 'left'; - context.textBaseline = 'middle'; - context.fillText( message.text, 0, canvas.height/2); - - var texture = new THREE$1.Texture(canvas); - texture.needsUpdate = true; - - var spriteMaterial = new THREE$1.SpriteMaterial({ - map: texture, - // NOTE: This is needed for THREE.js r61, unused in r70 - useScreenCoordinates: false }); - var sprite = new THREE$1.Sprite( spriteMaterial ); - var textSize = message.scale.x; - sprite.scale.set(textWidth / canvas.height * textSize, textSize, 1); - - this.add(sprite); } - break; - case MARKER_MESH_RESOURCE: - // load and add the mesh - var meshColorMaterial = null; - if(message.color.r !== 0 || message.color.g !== 0 || - message.color.b !== 0 || message.color.a !== 0) { - meshColorMaterial = colorMaterial; - } - this.msgMesh = message.mesh_resource.substr(10); - var meshResource = new MeshResource({ - path : path, - resource : this.msgMesh, - material : meshColorMaterial, - }); - this.add(meshResource); - break; - case MARKER_TRIANGLE_LIST: - // create the list of triangles - var tri = new TriangleList({ - material : colorMaterial, - vertices : message.points, - colors : message.colors - }); - tri.scale.set(message.scale.x, message.scale.y, message.scale.z); - this.add(tri); - break; - default: - console.error('Currently unsupported marker type: ' + message.type); - break; - } - }; - - /** - * Set the pose of this marker to the given values. - * - * @param pose - the pose to set for this marker - */ - setPose(pose) { - // set position information - this.position.x = pose.position.x; - this.position.y = pose.position.y; - this.position.z = pose.position.z; - - // set the rotation - this.quaternion.set(pose.orientation.x, pose.orientation.y, - pose.orientation.z, pose.orientation.w); - this.quaternion.normalize(); - - // update the world - this.updateMatrixWorld(); - }; - - /** - * Update this marker. - * - * @param message - the marker message - * @return true on success otherwhise false is returned - */ - update(message) { - // set the pose and get the color - this.setPose(message.pose); - - // Update color - if(message.color.r !== this.msgColor.r || - message.color.g !== this.msgColor.g || - message.color.b !== this.msgColor.b || - message.color.a !== this.msgColor.a) - { - var colorMaterial = makeColorMaterial( - message.color.r, message.color.g, - message.color.b, message.color.a); - - switch (message.type) { - case MARKER_LINE_STRIP: - case MARKER_LINE_LIST: - case MARKER_POINTS: - break; - case MARKER_ARROW: - case MARKER_CUBE: - case MARKER_SPHERE: - case MARKER_CYLINDER: - case MARKER_TRIANGLE_LIST: - case MARKER_TEXT_VIEW_FACING: - this.traverse (function (child){ - if (child instanceof THREE$1.Mesh) { - child.material = colorMaterial; - } - }); - break; - case MARKER_MESH_RESOURCE: - var meshColorMaterial = null; - if(message.color.r !== 0 || message.color.g !== 0 || - message.color.b !== 0 || message.color.a !== 0) { - meshColorMaterial = this.colorMaterial; - } - this.traverse (function (child){ - if (child instanceof THREE$1.Mesh) { - child.material = meshColorMaterial; - } - }); - break; - case MARKER_CUBE_LIST: - case MARKER_SPHERE_LIST: - // TODO Support to update color for MARKER_CUBE_LIST & MARKER_SPHERE_LIST - return false; - default: - return false; - } - - this.msgColor = message.color; - } - - // Update geometry - var scaleChanged = - Math.abs(this.msgScale[0] - message.scale.x) > 1.0e-6 || - Math.abs(this.msgScale[1] - message.scale.y) > 1.0e-6 || - Math.abs(this.msgScale[2] - message.scale.z) > 1.0e-6; - this.msgScale = [message.scale.x, message.scale.y, message.scale.z]; - - switch (message.type) { - case MARKER_CUBE: - case MARKER_SPHERE: - case MARKER_CYLINDER: - if(scaleChanged) { - return false; - } - break; - case MARKER_TEXT_VIEW_FACING: - if(scaleChanged || this.text !== message.text) { - return false; - } - break; - case MARKER_MESH_RESOURCE: - var meshResource = message.mesh_resource.substr(10); - if(meshResource !== this.msgMesh) { - return false; - } - if(scaleChanged) { - return false; - } - break; - case MARKER_ARROW: - case MARKER_LINE_STRIP: - case MARKER_LINE_LIST: - case MARKER_CUBE_LIST: - case MARKER_SPHERE_LIST: - case MARKER_POINTS: - case MARKER_TRIANGLE_LIST: - // TODO: Check if geometry changed - return false; - default: - break; - } - - return true; - }; - - /* - * Free memory of elements in this marker. - */ - dispose() { - this.children.forEach(function(element) { - if (element instanceof MeshResource) { - element.children.forEach(function(scene) { - if (scene.material !== undefined) { - scene.material.dispose(); - } - scene.children.forEach(function(mesh) { - if (mesh.geometry !== undefined) { - mesh.geometry.dispose(); - } - if (mesh.material !== undefined) { - mesh.material.dispose(); - } - scene.remove(mesh); - }); - element.remove(scene); - }); - } else { - if (element.geometry !== undefined) { - element.geometry.dispose(); - } - if (element.material !== undefined) { - element.material.dispose(); - } - } - element.parent.remove(element); + }, + vertexShader : this.vertex_shader, + fragmentShader : this.fragment_shader }); - }; -} + + this.mesh = new THREE.ParticleSystem(this.geometry, this.material); + this.mesh.position.x = 0; + this.mesh.position.y = 0; + this.add(this.mesh); + + var that = this; + + setInterval(function() { + if (that.video.readyState === that.video.HAVE_ENOUGH_DATA) { + that.texture.needsUpdate = true; + } + }, 1000 / 30); + } +}; + +/** + * Start video playback + */ +ROS3D.DepthCloud.prototype.startStream = function() { + this.video.play(); +}; + +/** + * Stop video playback + */ +ROS3D.DepthCloud.prototype.stopStream = function() { + this.video.pause(); +}; /** * @author David Gossow - dgossow@willowgarage.com */ -class InteractiveMarkerControl extends THREE$1.Object3D { +/** + * The main interactive marker object. + * + * @constructor + * @param options - object with following keys: + * + * * handle - the ROS3D.InteractiveMarkerHandle for this marker + * * camera - the main camera associated with the viewer for this marker + * * path (optional) - the base path to any meshes that will be loaded + * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER + * ROS3D.COLLADA_LOADER_2) -- defaults to ROS3D.COLLADA_LOADER_2 + */ +ROS3D.InteractiveMarker = function(options) { + THREE.Object3D.call(this); + THREE.EventDispatcher.call(this); - /** - * The main marker control object for an interactive marker. - * - * @constructor - * @param options - object with following keys: - * - * * parent - the parent of this control - * * message - the interactive marker control message - * * camera - the main camera associated with the viewer for this marker client - * * path (optional) - the base path to any meshes that will be loaded - * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER) - */ - constructor(options) { - super(); - var that = this; + var that = this; + options = options || {}; + var handle = options.handle; + this.name = handle.name; + var camera = options.camera; + var path = options.path || '/'; + var loader = options.loader || ROS3D.COLLADA_LOADER_2; + this.dragging = false; - options = options || {}; - this.parent = options.parent; - var handle = options.handle; - var message = options.message; - this.message = message; - this.name = message.name; - this.camera = options.camera; - this.path = options.path || '/'; - this.loader = options.loader; + // set the initial pose + this.onServerSetPose({ + pose : handle.pose + }); + + // information on where the drag started + this.dragStart = { + position : new THREE.Vector3(), + orientation : new THREE.Quaternion(), + positionWorld : new THREE.Vector3(), + orientationWorld : new THREE.Quaternion(), + event3d : {} + }; + + // add each control message + handle.controls.forEach(function(controlMessage) { + that.add(new ROS3D.InteractiveMarkerControl({ + parent : that, + handle : handle, + message : controlMessage, + camera : camera, + path : path, + loader : loader + })); + }); + + // check for any menus + if (handle.menuEntries.length > 0) { + this.menu = new ROS3D.InteractiveMarkerMenu({ + menuEntries : handle.menuEntries, + menuFontSize : handle.menuFontSize + }); + + // forward menu select events + this.menu.addEventListener('menu-select', function(event) { + that.dispatchEvent(event); + }); + } +}; +ROS3D.InteractiveMarker.prototype.__proto__ = THREE.Object3D.prototype; + +/** + * Show the interactive marker menu associated with this marker. + * + * @param control - the control to use + * @param event - the event that caused this + */ +ROS3D.InteractiveMarker.prototype.showMenu = function(control, event) { + if (this.menu) { + this.menu.show(control, event); + } +}; + +/** + * Move the axis based on the given event information. + * + * @param control - the control to use + * @param origAxis - the origin of the axis + * @param event3d - the event that caused this + */ +ROS3D.InteractiveMarker.prototype.moveAxis = function(control, origAxis, event3d) { + if (this.dragging) { + var currentControlOri = control.currentControlOri; + var axis = origAxis.clone().applyQuaternion(currentControlOri); + // get move axis in world coords + var originWorld = this.dragStart.event3d.intersection.point; + var axisWorld = axis.clone().applyQuaternion(this.dragStart.orientationWorld.clone()); + + var axisRay = new THREE.Ray(originWorld, axisWorld); + + // find closest point to mouse on axis + var t = ROS3D.closestAxisPoint(axisRay, event3d.camera, event3d.mousePos); + + // offset from drag start position + var p = new THREE.Vector3(); + p.addVectors(this.dragStart.position, axis.clone().applyQuaternion(this.dragStart.orientation) + .multiplyScalar(t)); + this.setPosition(control, p); + + event3d.stopPropagation(); + } +}; + +/** + * Move with respect to the plane based on the contorl and event. + * + * @param control - the control to use + * @param origNormal - the normal of the origin + * @param event3d - the event that caused this + */ +ROS3D.InteractiveMarker.prototype.movePlane = function(control, origNormal, event3d) { + if (this.dragging) { + var currentControlOri = control.currentControlOri; + var normal = origNormal.clone().applyQuaternion(currentControlOri); + // get plane params in world coords + var originWorld = this.dragStart.event3d.intersection.point; + var normalWorld = normal.clone().applyQuaternion(this.dragStart.orientationWorld); + + // intersect mouse ray with plane + var intersection = ROS3D.intersectPlane(event3d.mouseRay, originWorld, normalWorld); + + // offset from drag start position + var p = new THREE.Vector3(); + p.subVectors(intersection, originWorld); + p.add(this.dragStart.positionWorld); + this.setPosition(control, p); + event3d.stopPropagation(); + } +}; + +/** + * Rotate based on the control and event given. + * + * @param control - the control to use + * @param origOrientation - the orientation of the origin + * @param event3d - the event that caused this + */ +ROS3D.InteractiveMarker.prototype.rotateAxis = function(control, origOrientation, event3d) { + if (this.dragging) { + control.updateMatrixWorld(); + + var currentControlOri = control.currentControlOri; + var orientation = currentControlOri.clone().multiply(origOrientation.clone()); + + var normal = (new THREE.Vector3(1, 0, 0)).applyQuaternion(orientation); + + // get plane params in world coords + var originWorld = this.dragStart.event3d.intersection.point; + var normalWorld = normal.applyQuaternion(this.dragStart.orientationWorld); + + // intersect mouse ray with plane + var intersection = ROS3D.intersectPlane(event3d.mouseRay, originWorld, normalWorld); + + // offset local origin to lie on intersection plane + var normalRay = new THREE.Ray(this.dragStart.positionWorld, normalWorld); + var rotOrigin = ROS3D.intersectPlane(normalRay, originWorld, normalWorld); + + // rotates from world to plane coords + var orientationWorld = this.dragStart.orientationWorld.clone().multiply(orientation); + var orientationWorldInv = orientationWorld.clone().inverse(); + + // rotate original and current intersection into local coords + intersection.sub(rotOrigin); + intersection.applyQuaternion(orientationWorldInv); + + var origIntersection = this.dragStart.event3d.intersection.point.clone(); + origIntersection.sub(rotOrigin); + origIntersection.applyQuaternion(orientationWorldInv); + + // compute relative 2d angle + var a1 = Math.atan2(intersection.y, intersection.z); + var a2 = Math.atan2(origIntersection.y, origIntersection.z); + var a = a2 - a1; + + var rot = new THREE.Quaternion(); + rot.setFromAxisAngle(normal, a); + + // rotate + this.setOrientation(control, rot.multiply(this.dragStart.orientationWorld)); + + // offset from drag start position + event3d.stopPropagation(); + } +}; + +/** + * Dispatch the given event type. + * + * @param type - the type of event + * @param control - the control to use + */ +ROS3D.InteractiveMarker.prototype.feedbackEvent = function(type, control) { + this.dispatchEvent({ + type : type, + position : this.position.clone(), + orientation : this.quaternion.clone(), + controlName : control.name + }); +}; + +/** + * Start a drag action. + * + * @param control - the control to use + * @param event3d - the event that caused this + */ +ROS3D.InteractiveMarker.prototype.startDrag = function(control, event3d) { + if (event3d.domEvent.button === 0) { + event3d.stopPropagation(); + this.dragging = true; + this.updateMatrixWorld(true); + var scale = new THREE.Vector3(); + this.matrixWorld + .decompose(this.dragStart.positionWorld, this.dragStart.orientationWorld, scale); + this.dragStart.position = this.position.clone(); + this.dragStart.orientation = this.quaternion.clone(); + this.dragStart.event3d = event3d; + + this.feedbackEvent('user-mousedown', control); + } +}; + +/** + * Stop a drag action. + * + * @param control - the control to use + * @param event3d - the event that caused this + */ +ROS3D.InteractiveMarker.prototype.stopDrag = function(control, event3d) { + if (event3d.domEvent.button === 0) { + event3d.stopPropagation(); this.dragging = false; - this.startMousePos = new THREE$1.Vector2(); + this.dragStart.event3d = {}; + this.onServerSetPose(this.bufferedPoseEvent); + this.bufferedPoseEvent = undefined; - // orientation for the control - var controlOri = new THREE$1.Quaternion(message.orientation.x, message.orientation.y, - message.orientation.z, message.orientation.w); - controlOri.normalize(); + this.feedbackEvent('user-mouseup', control); + } +}; - // transform x axis into local frame - var controlAxis = new THREE$1.Vector3(1, 0, 0); - controlAxis.applyQuaternion(controlOri); +/** + * Handle a button click. + * + * @param control - the control to use + * @param event3d - the event that caused this + */ +ROS3D.InteractiveMarker.prototype.buttonClick = function(control, event3d) { + event3d.stopPropagation(); + this.feedbackEvent('user-button-click', control); +}; - this.currentControlOri = new THREE$1.Quaternion(); +/** + * Handle a user pose change for the position. + * + * @param control - the control to use + * @param event3d - the event that caused this + */ +ROS3D.InteractiveMarker.prototype.setPosition = function(control, position) { + this.position = position; + this.feedbackEvent('user-pose-change', control); +}; - // determine mouse interaction - switch (message.interaction_mode) { - case INTERACTIVE_MARKER_MOVE_AXIS: - this.addEventListener('mousemove', this.parent.moveAxis.bind(this.parent, this, controlAxis)); - this.addEventListener('touchmove', this.parent.moveAxis.bind(this.parent, this, controlAxis)); - break; - case INTERACTIVE_MARKER_ROTATE_AXIS: - this - .addEventListener('mousemove', this.parent.rotateAxis.bind(this.parent, this, controlOri)); - break; - case INTERACTIVE_MARKER_MOVE_PLANE: - this - .addEventListener('mousemove', this.parent.movePlane.bind(this.parent, this, controlAxis)); - break; - case INTERACTIVE_MARKER_BUTTON: - this.addEventListener('click', this.parent.buttonClick.bind(this.parent, this)); - break; - default: - break; +/** + * Handle a user pose change for the orientation. + * + * @param control - the control to use + * @param event3d - the event that caused this + */ +ROS3D.InteractiveMarker.prototype.setOrientation = function(control, orientation) { + orientation.normalize(); + this.quaternion = orientation; + this.feedbackEvent('user-pose-change', control); +}; + +/** + * Update the marker based when the pose is set from the server. + * + * @param event - the event that caused this + */ +ROS3D.InteractiveMarker.prototype.onServerSetPose = function(event) { + if (event !== undefined) { + // don't update while dragging + if (this.dragging) { + this.bufferedPoseEvent = event; + } else { + var pose = event.pose; + + this.position.x = pose.position.x; + this.position.y = pose.position.y; + this.position.z = pose.position.z; + + this.quaternion = new THREE.Quaternion(pose.orientation.x, pose.orientation.y, + pose.orientation.z, pose.orientation.w); + + this.updateMatrixWorld(true); + } + } +}; + +/** + * Free memory of elements in this marker. + */ +ROS3D.InteractiveMarker.prototype.dispose = function() { + var that = this; + this.children.forEach(function(intMarkerControl) { + intMarkerControl.children.forEach(function(marker) { + marker.dispose(); + intMarkerControl.remove(marker); + }); + that.remove(intMarkerControl); + }); +}; + +THREE.EventDispatcher.prototype.apply( ROS3D.InteractiveMarker.prototype ); + +/** + * @author David Gossow - dgossow@willowgarage.com + */ + +/** + * A client for an interactive marker topic. + * + * @constructor + * @param options - object with following keys: + * + * * ros - a handle to the ROS connection + * * tfClient - a handle to the TF client + * * topic (optional) - the topic to subscribe to, like '/basic_controls' + * * path (optional) - the base path to any meshes that will be loaded + * * camera - the main camera associated with the viewer for this marker client + * * rootObject (optional) - the root THREE 3D object to render to + * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER + * ROS3D.COLLADA_LOADER_2) -- defaults to ROS3D.COLLADA_LOADER_2 + * * menuFontSize (optional) - the menu font size + */ +ROS3D.InteractiveMarkerClient = function(options) { + var that = this; + options = options || {}; + this.ros = options.ros; + this.tfClient = options.tfClient; + this.topic = options.topic; + this.path = options.path || '/'; + this.camera = options.camera; + this.rootObject = options.rootObject || new THREE.Object3D(); + this.loader = options.loader || ROS3D.COLLADA_LOADER_2; + this.menuFontSize = options.menuFontSize || '0.8em'; + + this.interactiveMarkers = {}; + this.updateTopic = null; + this.feedbackTopic = null; + + // check for an initial topic + if (this.topic) { + this.subscribe(this.topic); + } +}; + +/** + * Subscribe to the given interactive marker topic. This will unsubscribe from any current topics. + * + * @param topic - the topic to subscribe to, like '/basic_controls' + */ +ROS3D.InteractiveMarkerClient.prototype.subscribe = function(topic) { + // unsubscribe to the other topics + this.unsubscribe(); + + this.updateTopic = new ROSLIB.Topic({ + ros : this.ros, + name : topic + '/tunneled/update', + messageType : 'visualization_msgs/InteractiveMarkerUpdate', + compression : 'png' + }); + this.updateTopic.subscribe(this.processUpdate.bind(this)); + + this.feedbackTopic = new ROSLIB.Topic({ + ros : this.ros, + name : topic + '/feedback', + messageType : 'visualization_msgs/InteractiveMarkerFeedback', + compression : 'png' + }); + this.feedbackTopic.advertise(); + + this.initService = new ROSLIB.Service({ + ros : this.ros, + name : topic + '/tunneled/get_init', + serviceType : 'demo_interactive_markers/GetInit' + }); + var request = new ROSLIB.ServiceRequest({}); + this.initService.callService(request, this.processInit.bind(this)); +}; + +/** + * Unsubscribe from the current interactive marker topic. + */ +ROS3D.InteractiveMarkerClient.prototype.unsubscribe = function() { + if (this.updateTopic) { + this.updateTopic.unsubscribe(); + } + if (this.feedbackTopic) { + this.feedbackTopic.unadvertise(); + } + // erase all markers + for (var intMarkerName in this.interactiveMarkers) { + this.eraseIntMarker(intMarkerName); + } + this.interactiveMarkers = {}; +}; + +/** + * Process the given interactive marker initialization message. + * + * @param initMessage - the interactive marker initialization message to process + */ +ROS3D.InteractiveMarkerClient.prototype.processInit = function(initMessage) { + var message = initMessage.msg; + + // erase any old markers + message.erases = []; + for (var intMarkerName in this.interactiveMarkers) { + message.erases.push(intMarkerName); + } + message.poses = []; + + // treat it as an update + this.processUpdate(message); +}; + +/** + * Process the given interactive marker update message. + * + * @param initMessage - the interactive marker update message to process + */ +ROS3D.InteractiveMarkerClient.prototype.processUpdate = function(message) { + var that = this; + + // erase any markers + message.erases.forEach(function(name) { + that.eraseIntMarker(name); + }); + + // updates marker poses + message.poses.forEach(function(poseMessage) { + var marker = that.interactiveMarkers[poseMessage.name]; + if (marker) { + marker.setPoseFromServer(poseMessage.pose); + } + }); + + // add new markers + message.markers.forEach(function(msg) { + // get rid of anything with the same name + var oldhandle = that.interactiveMarkers[msg.name]; + if (oldhandle) { + that.eraseIntMarker(oldhandle.name); } - /** - * Install default listeners for highlighting / dragging. - * - * @param event - the event to stop - */ - function stopPropagation(event) { - event.stopPropagation(); - } + // create the handle + var handle = new ROS3D.InteractiveMarkerHandle({ + message : msg, + feedbackTopic : that.feedbackTopic, + tfClient : that.tfClient, + menuFontSize : that.menuFontSize + }); + that.interactiveMarkers[msg.name] = handle; - // check the mode - if (message.interaction_mode !== INTERACTIVE_MARKER_NONE) { - this.addEventListener('mousedown', this.parent.startDrag.bind(this.parent, this)); - this.addEventListener('mouseup', this.parent.stopDrag.bind(this.parent, this)); - this.addEventListener('contextmenu', this.parent.showMenu.bind(this.parent, this)); - this.addEventListener('mouseup', function(event3d) { - if (that.startMousePos.distanceToSquared(event3d.mousePos) === 0) { - event3d.type = 'contextmenu'; - that.dispatchEvent(event3d); - } - }); - this.addEventListener('mouseover', stopPropagation); - this.addEventListener('mouseout', stopPropagation); - this.addEventListener('click', stopPropagation); - this.addEventListener('mousedown', function(event3d) { - that.startMousePos = event3d.mousePos; - }); + // create the actual marker + var intMarker = new ROS3D.InteractiveMarker({ + handle : handle, + camera : that.camera, + path : that.path, + loader : that.loader + }); + // add it to the scene + intMarker.name = msg.name; + that.rootObject.add(intMarker); - // touch support - this.addEventListener('touchstart', function(event3d) { - if (event3d.domEvent.touches.length === 1) { - event3d.type = 'mousedown'; - event3d.domEvent.button = 0; - that.dispatchEvent(event3d); - } + // listen for any pose updates from the server + handle.on('pose', function(pose) { + intMarker.onServerSetPose({ + pose : pose }); - this.addEventListener('touchmove', function(event3d) { - if (event3d.domEvent.touches.length === 1) { - event3d.type = 'mousemove'; - event3d.domEvent.button = 0; - that.dispatchEvent(event3d); - } - }); - this.addEventListener('touchend', function(event3d) { - if (event3d.domEvent.touches.length === 0) { - event3d.domEvent.button = 0; - event3d.type = 'mouseup'; - that.dispatchEvent(event3d); - event3d.type = 'click'; - that.dispatchEvent(event3d); - } - }); - } - - // rotation behavior - var rotInv = new THREE$1.Quaternion(); - var posInv = this.parent.position.clone().multiplyScalar(-1); - switch (message.orientation_mode) { - case INTERACTIVE_MARKER_INHERIT: - rotInv = this.parent.quaternion.clone().inverse(); - break; - case INTERACTIVE_MARKER_FIXED: - break; - case INTERACTIVE_MARKER_VIEW_FACING: - break; - default: - console.error('Unkown orientation mode: ' + message.orientation_mode); - break; - } - - // temporary TFClient to get transformations from InteractiveMarker - // frame to potential child Marker frames - var localTfClient = new ROSLIB.TFClient({ - ros : handle.tfClient.ros, - fixedFrame : handle.message.header.frame_id, - serverName : handle.tfClient.serverName }); - // create visuals (markers) - message.markers.forEach(function(markerMsg) { - var addMarker = function(transformMsg) { - var markerHelper = new Marker({ - message : markerMsg, - path : that.path, - loader : that.loader - }); + intMarker.addEventListener('user-pose-change', handle.setPoseFromClient.bind(handle)); + intMarker.addEventListener('user-mousedown', handle.onMouseDown.bind(handle)); + intMarker.addEventListener('user-mouseup', handle.onMouseUp.bind(handle)); + intMarker.addEventListener('user-button-click', handle.onButtonClick.bind(handle)); + intMarker.addEventListener('menu-select', handle.onMenuSelect.bind(handle)); - // if transformMsg isn't null, this was called by TFClient - if (transformMsg !== null) { - // get the current pose as a ROSLIB.Pose... - var newPose = new ROSLIB.Pose({ - position : markerHelper.position, - orientation : markerHelper.quaternion - }); - // so we can apply the transform provided by the TFClient - newPose.applyTransform(new ROSLIB.Transform(transformMsg)); + // now list for any TF changes + handle.subscribeTf(); + }); +}; - // get transform between parent marker's location and its frame - // apply it to sub-marker position to get sub-marker position - // relative to parent marker - var transformMarker = new Marker({ - message : markerMsg, - path : that.path, - loader : that.loader - }); - transformMarker.position.add(posInv); - transformMarker.position.applyQuaternion(rotInv); - transformMarker.quaternion.multiplyQuaternions(rotInv, transformMarker.quaternion); - var translation = new THREE$1.Vector3(transformMarker.position.x, transformMarker.position.y, transformMarker.position.z); - var transform = new ROSLIB.Transform({ - translation : translation, - orientation : transformMarker.quaternion - }); +/** + * Erase the interactive marker with the given name. + * + * @param intMarkerName - the interactive marker name to delete + */ +ROS3D.InteractiveMarkerClient.prototype.eraseIntMarker = function(intMarkerName) { + if (this.interactiveMarkers[intMarkerName]) { + // remove the object + var targetIntMarker = this.rootObject.getObjectByName(intMarkerName); + this.rootObject.remove(targetIntMarker); + delete this.interactiveMarkers[intMarkerName]; + targetIntMarker.dispose(); + } +}; - // apply that transform too - newPose.applyTransform(transform); +/** + * @author David Gossow - dgossow@willowgarage.com + */ - markerHelper.setPose(newPose); +/** + * The main marker control object for an interactive marker. + * + * @constructor + * @param options - object with following keys: + * + * * parent - the parent of this control + * * message - the interactive marker control message + * * camera - the main camera associated with the viewer for this marker client + * * path (optional) - the base path to any meshes that will be loaded + * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER + * ROS3D.COLLADA_LOADER_2) -- defaults to ROS3D.COLLADA_LOADER_2 + */ +ROS3D.InteractiveMarkerControl = function(options) { + var that = this; + THREE.Object3D.call(this); - markerHelper.updateMatrixWorld(); - // we only need to set the pose once - at least, this is what RViz seems to be doing, might change in the future - localTfClient.unsubscribe(markerMsg.header.frame_id); - } + options = options || {}; + this.parent = options.parent; + var handle = options.handle; + var message = options.message; + this.name = message.name; + this.camera = options.camera; + this.path = options.path || '/'; + this.loader = options.loader || ROS3D.COLLADA_LOADER_2; + this.dragging = false; + this.startMousePos = new THREE.Vector2(); - // add the marker - that.add(markerHelper); - }; + // orientation for the control + var controlOri = new THREE.Quaternion(message.orientation.x, message.orientation.y, + message.orientation.z, message.orientation.w); + controlOri.normalize(); - // If the marker is not relative to the parent marker's position, - // ask the *local* TFClient for the transformation from the - // InteractiveMarker frame to the sub-Marker frame - if (markerMsg.header.frame_id !== '') { - localTfClient.subscribe(markerMsg.header.frame_id, addMarker); - } - // If not, just add the marker without changing its pose - else { - addMarker(null); + // transform x axis into local frame + var controlAxis = new THREE.Vector3(1, 0, 0); + controlAxis.applyQuaternion(controlOri); + + this.currentControlOri = new THREE.Quaternion(); + + // determine mouse interaction + switch (message.interaction_mode) { + case ROS3D.INTERACTIVE_MARKER_MOVE_AXIS: + this.addEventListener('mousemove', this.parent.moveAxis.bind(this.parent, this, controlAxis)); + this.addEventListener('touchmove', this.parent.moveAxis.bind(this.parent, this, controlAxis)); + break; + case ROS3D.INTERACTIVE_MARKER_ROTATE_AXIS: + this + .addEventListener('mousemove', this.parent.rotateAxis.bind(this.parent, this, controlOri)); + break; + case ROS3D.INTERACTIVE_MARKER_MOVE_PLANE: + this + .addEventListener('mousemove', this.parent.movePlane.bind(this.parent, this, controlAxis)); + break; + case ROS3D.INTERACTIVE_MARKER_BUTTON: + this.addEventListener('click', this.parent.buttonClick.bind(this.parent, this)); + break; + default: + break; + } + + /** + * Install default listeners for highlighting / dragging. + * + * @param event - the event to stop + */ + function stopPropagation(event) { + event.stopPropagation(); + } + + // check the mode + if (message.interaction_mode !== ROS3D.INTERACTIVE_MARKER_NONE) { + this.addEventListener('mousedown', this.parent.startDrag.bind(this.parent, this)); + this.addEventListener('mouseup', this.parent.stopDrag.bind(this.parent, this)); + this.addEventListener('contextmenu', this.parent.showMenu.bind(this.parent, this)); + this.addEventListener('mouseup', function(event3d) { + if (that.startMousePos.distanceToSquared(event3d.mousePos) === 0) { + event3d.type = 'contextmenu'; + that.dispatchEvent(event3d); } }); - }; + this.addEventListener('mouseover', stopPropagation); + this.addEventListener('mouseout', stopPropagation); + this.addEventListener('click', stopPropagation); + this.addEventListener('mousedown', function(event3d) { + that.startMousePos = event3d.mousePos; + }); - updateMatrixWorld (force) { - var that = this; - var message = this.message; - switch (message.orientation_mode) { - case INTERACTIVE_MARKER_INHERIT: - super.updateMatrixWorld(force); + // touch support + this.addEventListener('touchstart', function(event3d) { + if (event3d.domEvent.touches.length === 1) { + event3d.type = 'mousedown'; + event3d.domEvent.button = 0; + that.dispatchEvent(event3d); + } + }); + this.addEventListener('touchmove', function(event3d) { + if (event3d.domEvent.touches.length === 1) { + event3d.type = 'mousemove'; + event3d.domEvent.button = 0; + that.dispatchEvent(event3d); + } + }); + this.addEventListener('touchend', function(event3d) { + if (event3d.domEvent.touches.length === 0) { + event3d.domEvent.button = 0; + event3d.type = 'mouseup'; + that.dispatchEvent(event3d); + event3d.type = 'click'; + that.dispatchEvent(event3d); + } + }); + } + + // rotation behavior + var rotInv = new THREE.Quaternion(); + var posInv = this.parent.position.clone().multiplyScalar(-1); + switch (message.orientation_mode) { + case ROS3D.INTERACTIVE_MARKER_INHERIT: + rotInv = this.parent.quaternion.clone().inverse(); + this.updateMatrixWorld = function(force) { + ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld.call(that, force); that.currentControlOri.copy(that.quaternion); that.currentControlOri.normalize(); - break; - case INTERACTIVE_MARKER_FIXED: - that.quaternion.copy(that.parent.quaternion.clone().inverse()); + }; + break; + case ROS3D.INTERACTIVE_MARKER_FIXED: + this.updateMatrixWorld = function(force) { + that.quaternion = that.parent.quaternion.clone().inverse(); that.updateMatrix(); that.matrixWorldNeedsUpdate = true; - super.updateMatrixWorld(force); + ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld.call(that, force); that.currentControlOri.copy(that.quaternion); - break; - case INTERACTIVE_MARKER_VIEW_FACING: + }; + break; + case ROS3D.INTERACTIVE_MARKER_VIEW_FACING: + var independentMarkerOrientation = message.independent_marker_orientation; + this.updateMatrixWorld = function(force) { that.camera.updateMatrixWorld(); - var cameraRot = new THREE$1.Matrix4().extractRotation(that.camera.matrixWorld); + var cameraRot = new THREE.Matrix4().extractRotation(that.camera.matrixWorld); - var ros2Gl = new THREE$1.Matrix4(); + var ros2Gl = new THREE.Matrix4(); var r90 = Math.PI * 0.5; - var rv = new THREE$1.Euler(-r90, 0, r90); + var rv = new THREE.Euler(-r90, 0, r90); ros2Gl.makeRotationFromEuler(rv); - var worldToLocal = new THREE$1.Matrix4(); + var worldToLocal = new THREE.Matrix4(); worldToLocal.getInverse(that.parent.matrixWorld); cameraRot.multiplyMatrices(cameraRot, ros2Gl); @@ -50319,3744 +1156,2233 @@ class InteractiveMarkerControl extends THREE$1.Object3D { that.currentControlOri.setFromRotationMatrix(cameraRot); // check the orientation - if (!message.independent_marker_orientation) { + if (!independentMarkerOrientation) { that.quaternion.copy(that.currentControlOri); that.updateMatrix(); that.matrixWorldNeedsUpdate = true; } - super.updateMatrixWorld(force); - break; - default: - console.error('Unkown orientation mode: ' + message.orientation_mode); - break; - } - }; -} - -/** - * @author David Gossow - dgossow@willowgarage.com - */ - -class InteractiveMarkerMenu extends THREE$1.EventDispatcher { - - /** - * A menu for an interactive marker. This will be overlayed on the canvas. - * - * @constructor - * @param options - object with following keys: - * - * * menuEntries - the menu entries to add - * * className (optional) - a custom CSS class for the menu div - * * entryClassName (optional) - a custom CSS class for the menu entry - * * overlayClassName (optional) - a custom CSS class for the menu overlay - * * menuFontSize (optional) - the menu font size - */ - constructor(options) { - super(); - var that = this; - options = options || {}; - var menuEntries = options.menuEntries; - var className = options.className || 'default-interactive-marker-menu'; - var entryClassName = options.entryClassName || 'default-interactive-marker-menu-entry'; - var overlayClassName = options.overlayClassName || 'default-interactive-marker-overlay'; - var menuFontSize = options.menuFontSize || '0.8em'; - - // holds the menu tree - var allMenus = []; - allMenus[0] = { - children : [] - }; - - - // create the CSS for this marker if it has not been created - if (document.getElementById('default-interactive-marker-menu-css') === null) { - var style = document.createElement('style'); - style.id = 'default-interactive-marker-menu-css'; - style.type = 'text/css'; - style.innerHTML = '.default-interactive-marker-menu {' + 'background-color: #444444;' - + 'border: 1px solid #888888;' + 'border: 1px solid #888888;' + 'padding: 0px 0px 0px 0px;' - + 'color: #FFFFFF;' + 'font-family: sans-serif;' + 'font-size: ' + menuFontSize +';' + 'z-index: 1002;' - + '}' + '.default-interactive-marker-menu ul {' + 'padding: 0px 0px 5px 0px;' - + 'margin: 0px;' + 'list-style-type: none;' + '}' - + '.default-interactive-marker-menu ul li div {' + '-webkit-touch-callout: none;' - + '-webkit-user-select: none;' + '-khtml-user-select: none;' + '-moz-user-select: none;' - + '-ms-user-select: none;' + 'user-select: none;' + 'cursor: default;' - + 'padding: 3px 10px 3px 10px;' + '}' + '.default-interactive-marker-menu-entry:hover {' - + ' background-color: #666666;' + ' cursor: pointer;' + '}' - + '.default-interactive-marker-menu ul ul {' + ' font-style: italic;' - + ' padding-left: 10px;' + '}' + '.default-interactive-marker-overlay {' - + ' position: absolute;' + ' top: 0%;' + ' left: 0%;' + ' width: 100%;' - + ' height: 100%;' + ' background-color: black;' + ' z-index: 1001;' - + ' -moz-opacity: 0.0;' + ' opacity: .0;' + ' filter: alpha(opacity = 0);' + '}'; - document.getElementsByTagName('head')[0].appendChild(style); - } - - // place the menu in a div - this.menuDomElem = document.createElement('div'); - this.menuDomElem.style.position = 'absolute'; - this.menuDomElem.className = className; - this.menuDomElem.addEventListener('contextmenu', function(event) { - event.preventDefault(); - }); - - // create the overlay DOM - this.overlayDomElem = document.createElement('div'); - this.overlayDomElem.className = overlayClassName; - - this.hideListener = this.hide.bind(this); - this.overlayDomElem.addEventListener('contextmenu', this.hideListener); - this.overlayDomElem.addEventListener('click', this.hideListener); - this.overlayDomElem.addEventListener('touchstart', this.hideListener); - - // parse all entries and link children to parents - var i, entry, id; - for ( i = 0; i < menuEntries.length; i++) { - entry = menuEntries[i]; - id = entry.id; - allMenus[id] = { - title : entry.title, - id : id, - children : [] + ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld.call(that, force); }; - } - for ( i = 0; i < menuEntries.length; i++) { - entry = menuEntries[i]; - id = entry.id; - var menu = allMenus[id]; - var parent = allMenus[entry.parent_id]; - parent.children.push(menu); - } - - function emitMenuSelect(menuEntry, domEvent) { - this.dispatchEvent({ - type : 'menu-select', - domEvent : domEvent, - id : menuEntry.id, - controlName : this.controlName - }); - this.hide(domEvent); - } - - /** - * Create the HTML UL element for the menu and link it to the parent. - * - * @param parentDomElem - the parent DOM element - * @param parentMenu - the parent menu - */ - function makeUl(parentDomElem, parentMenu) { - - var ulElem = document.createElement('ul'); - parentDomElem.appendChild(ulElem); - - var children = parentMenu.children; - - for ( var i = 0; i < children.length; i++) { - var liElem = document.createElement('li'); - var divElem = document.createElement('div'); - divElem.appendChild(document.createTextNode(children[i].title)); - ulElem.appendChild(liElem); - liElem.appendChild(divElem); - - if (children[i].children.length > 0) { - makeUl(liElem, children[i]); - divElem.addEventListener('click', that.hide.bind(that)); - divElem.addEventListener('touchstart', that.hide.bind(that)); - } else { - divElem.addEventListener('click', emitMenuSelect.bind(that, children[i])); - divElem.addEventListener('touchstart', emitMenuSelect.bind(that, children[i])); - divElem.className = 'default-interactive-marker-menu-entry'; - } - } - - } - - // construct DOM element - makeUl(this.menuDomElem, allMenus[0]); - }; - - /** - * Shoe the menu DOM element. - * - * @param control - the control for the menu - * @param event - the event that caused this - */ - show(control, event) { - if (event && event.preventDefault) { - event.preventDefault(); - } - - this.controlName = control.name; - - // position it on the click - if (event.domEvent.changedTouches !== undefined) { - // touch click - this.menuDomElem.style.left = event.domEvent.changedTouches[0].pageX + 'px'; - this.menuDomElem.style.top = event.domEvent.changedTouches[0].pageY + 'px'; - } else { - // mouse click - this.menuDomElem.style.left = event.domEvent.clientX + 'px'; - this.menuDomElem.style.top = event.domEvent.clientY + 'px'; - } - document.body.appendChild(this.overlayDomElem); - document.body.appendChild(this.menuDomElem); - }; - - /** - * Hide the menu DOM element. - * - * @param event (optional) - the event that caused this - */ - hide(event) { - if (event && event.preventDefault) { - event.preventDefault(); - } - - document.body.removeChild(this.overlayDomElem); - document.body.removeChild(this.menuDomElem); - }; -} - -/** - * @author David Gossow - dgossow@willowgarage.com - */ - -class InteractiveMarker extends THREE$1.Object3D { - - /** - * The main interactive marker object. - * - * @constructor - * @param options - object with following keys: - * - * * handle - the ROS3D.InteractiveMarkerHandle for this marker - * * camera - the main camera associated with the viewer for this marker - * * path (optional) - the base path to any meshes that will be loaded - * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER) - */ - constructor(options) { - super(); - - var that = this; - options = options || {}; - var handle = options.handle; - this.name = handle.name; - var camera = options.camera; - var path = options.path || '/'; - var loader = options.loader; - this.dragging = false; - - // set the initial pose - this.onServerSetPose({ - pose : handle.pose - }); - - // information on where the drag started - this.dragStart = { - position : new THREE$1.Vector3(), - orientation : new THREE$1.Quaternion(), - positionWorld : new THREE$1.Vector3(), - orientationWorld : new THREE$1.Quaternion(), - event3d : {} - }; - - // add each control message - handle.controls.forEach(function(controlMessage) { - that.add(new InteractiveMarkerControl({ - parent : that, - handle : handle, - message : controlMessage, - camera : camera, - path : path, - loader : loader - })); - }); - - // check for any menus - if (handle.menuEntries.length > 0) { - this.menu = new InteractiveMarkerMenu({ - menuEntries : handle.menuEntries, - menuFontSize : handle.menuFontSize - }); - - // forward menu select events - this.menu.addEventListener('menu-select', function(event) { - that.dispatchEvent(event); - }); - } - }; - - /** - * Show the interactive marker menu associated with this marker. - * - * @param control - the control to use - * @param event - the event that caused this - */ - showMenu(control, event) { - if (this.menu) { - this.menu.show(control, event); - } - }; - - /** - * Move the axis based on the given event information. - * - * @param control - the control to use - * @param origAxis - the origin of the axis - * @param event3d - the event that caused this - */ - moveAxis(control, origAxis, event3d) { - if (this.dragging) { - var currentControlOri = control.currentControlOri; - var axis = origAxis.clone().applyQuaternion(currentControlOri); - // get move axis in world coords - var originWorld = this.dragStart.event3d.intersection.point; - var axisWorld = axis.clone().applyQuaternion(this.dragStart.orientationWorld.clone()); - - var axisRay = new THREE$1.Ray(originWorld, axisWorld); - - // find closest point to mouse on axis - var t = closestAxisPoint(axisRay, event3d.camera, event3d.mousePos); - - // offset from drag start position - var p = new THREE$1.Vector3(); - p.addVectors(this.dragStart.position, axis.clone().applyQuaternion(this.dragStart.orientation) - .multiplyScalar(t)); - this.setPosition(control, p); - - - event3d.stopPropagation(); - } - }; - - /** - * Move with respect to the plane based on the contorl and event. - * - * @param control - the control to use - * @param origNormal - the normal of the origin - * @param event3d - the event that caused this - */ - movePlane(control, origNormal, event3d) { - if (this.dragging) { - var currentControlOri = control.currentControlOri; - var normal = origNormal.clone().applyQuaternion(currentControlOri); - // get plane params in world coords - var originWorld = this.dragStart.event3d.intersection.point; - var normalWorld = normal.clone().applyQuaternion(this.dragStart.orientationWorld); - - // intersect mouse ray with plane - var intersection = intersectPlane(event3d.mouseRay, originWorld, normalWorld); - - // offset from drag start position - var p = new THREE$1.Vector3(); - p.subVectors(intersection, originWorld); - p.add(this.dragStart.positionWorld); - this.setPosition(control, p); - event3d.stopPropagation(); - } - }; - - /** - * Rotate based on the control and event given. - * - * @param control - the control to use - * @param origOrientation - the orientation of the origin - * @param event3d - the event that caused this - */ - rotateAxis(control, origOrientation, event3d) { - if (this.dragging) { - control.updateMatrixWorld(); - - var currentControlOri = control.currentControlOri; - var orientation = currentControlOri.clone().multiply(origOrientation.clone()); - - var normal = (new THREE$1.Vector3(1, 0, 0)).applyQuaternion(orientation); - - // get plane params in world coords - var originWorld = this.dragStart.event3d.intersection.point; - var normalWorld = normal.applyQuaternion(this.dragStart.orientationWorld); - - // intersect mouse ray with plane - var intersection = intersectPlane(event3d.mouseRay, originWorld, normalWorld); - - // offset local origin to lie on intersection plane - var normalRay = new THREE$1.Ray(this.dragStart.positionWorld, normalWorld); - var rotOrigin = intersectPlane(normalRay, originWorld, normalWorld); - - // rotates from world to plane coords - var orientationWorld = this.dragStart.orientationWorld.clone().multiply(orientation); - var orientationWorldInv = orientationWorld.clone().inverse(); - - // rotate original and current intersection into local coords - intersection.sub(rotOrigin); - intersection.applyQuaternion(orientationWorldInv); - - var origIntersection = this.dragStart.event3d.intersection.point.clone(); - origIntersection.sub(rotOrigin); - origIntersection.applyQuaternion(orientationWorldInv); - - // compute relative 2d angle - var a1 = Math.atan2(intersection.y, intersection.z); - var a2 = Math.atan2(origIntersection.y, origIntersection.z); - var a = a2 - a1; - - var rot = new THREE$1.Quaternion(); - rot.setFromAxisAngle(normal, a); - - // rotate - this.setOrientation(control, rot.multiply(this.dragStart.orientationWorld)); - - // offset from drag start position - event3d.stopPropagation(); - } - }; - - /** - * Dispatch the given event type. - * - * @param type - the type of event - * @param control - the control to use - */ - feedbackEvent(type, control) { - this.dispatchEvent({ - type : type, - position : this.position.clone(), - orientation : this.quaternion.clone(), - controlName : control.name - }); - }; - - /** - * Start a drag action. - * - * @param control - the control to use - * @param event3d - the event that caused this - */ - startDrag(control, event3d) { - if (event3d.domEvent.button === 0) { - event3d.stopPropagation(); - this.dragging = true; - this.updateMatrixWorld(true); - var scale = new THREE$1.Vector3(); - this.matrixWorld - .decompose(this.dragStart.positionWorld, this.dragStart.orientationWorld, scale); - this.dragStart.position = this.position.clone(); - this.dragStart.orientation = this.quaternion.clone(); - this.dragStart.event3d = event3d; - - this.feedbackEvent('user-mousedown', control); - } - }; - - /** - * Stop a drag action. - * - * @param control - the control to use - * @param event3d - the event that caused this - */ - stopDrag(control, event3d) { - if (event3d.domEvent.button === 0) { - event3d.stopPropagation(); - this.dragging = false; - this.dragStart.event3d = {}; - this.onServerSetPose(this.bufferedPoseEvent); - this.bufferedPoseEvent = undefined; - - this.feedbackEvent('user-mouseup', control); - } - }; - - /** - * Handle a button click. - * - * @param control - the control to use - * @param event3d - the event that caused this - */ - buttonClick(control, event3d) { - event3d.stopPropagation(); - this.feedbackEvent('user-button-click', control); - }; - - /** - * Handle a user pose change for the position. - * - * @param control - the control to use - * @param event3d - the event that caused this - */ - setPosition(control, position) { - this.position.copy(position); - this.feedbackEvent('user-pose-change', control); - }; - - /** - * Handle a user pose change for the orientation. - * - * @param control - the control to use - * @param event3d - the event that caused this - */ - setOrientation(control, orientation) { - orientation.normalize(); - this.quaternion.copy(orientation); - this.feedbackEvent('user-pose-change', control); - }; - - /** - * Update the marker based when the pose is set from the server. - * - * @param event - the event that caused this - */ - onServerSetPose(event) { - if (event !== undefined) { - // don't update while dragging - if (this.dragging) { - this.bufferedPoseEvent = event; - } else { - var pose = event.pose; - this.position.copy(pose.position); - this.quaternion.copy(pose.orientation); - this.updateMatrixWorld(true); - } - } - }; - - /** - * Free memory of elements in this marker. - */ - dispose() { - var that = this; - this.children.forEach(function(intMarkerControl) { - intMarkerControl.children.forEach(function(marker) { - marker.dispose(); - intMarkerControl.remove(marker); - }); - that.remove(intMarkerControl); - }); - }; -} - -function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; -} - -var eventemitter2 = createCommonjsModule(function (module, exports) { -!function(undefined) { - - var isArray = Array.isArray ? Array.isArray : function _isArray(obj) { - return Object.prototype.toString.call(obj) === "[object Array]"; - }; - var defaultMaxListeners = 10; - - function init() { - this._events = {}; - if (this._conf) { - configure.call(this, this._conf); - } + break; + default: + console.error('Unkown orientation mode: ' + message.orientation_mode); + break; } - function configure(conf) { - if (conf) { - this._conf = conf; - - conf.delimiter && (this.delimiter = conf.delimiter); - this._events.maxListeners = conf.maxListeners !== undefined ? conf.maxListeners : defaultMaxListeners; - conf.wildcard && (this.wildcard = conf.wildcard); - conf.newListener && (this.newListener = conf.newListener); - conf.verboseMemoryLeak && (this.verboseMemoryLeak = conf.verboseMemoryLeak); - - if (this.wildcard) { - this.listenerTree = {}; - } - } else { - this._events.maxListeners = defaultMaxListeners; - } - } - - function logPossibleMemoryLeak(count, eventName) { - var errorMsg = '(node) warning: possible EventEmitter memory ' + - 'leak detected. %d listeners added. ' + - 'Use emitter.setMaxListeners() to increase limit.'; - - if(this.verboseMemoryLeak){ - errorMsg += ' Event name: %s.'; - console.error(errorMsg, count, eventName); - } else { - console.error(errorMsg, count); - } - - if (console.trace){ - console.trace(); - } - } - - function EventEmitter(conf) { - this._events = {}; - this.newListener = false; - this.verboseMemoryLeak = false; - configure.call(this, conf); - } - EventEmitter.EventEmitter2 = EventEmitter; // backwards compatibility for exporting EventEmitter property - - // - // Attention, function return type now is array, always ! - // It has zero elements if no any matches found and one or more - // elements (leafs) if there are matches - // - function searchListenerTree(handlers, type, tree, i) { - if (!tree) { - return []; - } - var listeners=[], leaf, len, branch, xTree, xxTree, isolatedBranch, endReached, - typeLength = type.length, currentType = type[i], nextType = type[i+1]; - if (i === typeLength && tree._listeners) { - // - // If at the end of the event(s) list and the tree has listeners - // invoke those listeners. - // - if (typeof tree._listeners === 'function') { - handlers && handlers.push(tree._listeners); - return [tree]; - } else { - for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) { - handlers && handlers.push(tree._listeners[leaf]); - } - return [tree]; - } - } - - if ((currentType === '*' || currentType === '**') || tree[currentType]) { - // - // If the event emitted is '*' at this part - // or there is a concrete match at this patch - // - if (currentType === '*') { - for (branch in tree) { - if (branch !== '_listeners' && tree.hasOwnProperty(branch)) { - listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+1)); - } - } - return listeners; - } else if(currentType === '**') { - endReached = (i+1 === typeLength || (i+2 === typeLength && nextType === '*')); - if(endReached && tree._listeners) { - // The next element has a _listeners, add it to the handlers. - listeners = listeners.concat(searchListenerTree(handlers, type, tree, typeLength)); - } - - for (branch in tree) { - if (branch !== '_listeners' && tree.hasOwnProperty(branch)) { - if(branch === '*' || branch === '**') { - if(tree[branch]._listeners && !endReached) { - listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], typeLength)); - } - listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i)); - } else if(branch === nextType) { - listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+2)); - } else { - // No match on this one, shift into the tree but not in the type array. - listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i)); - } - } - } - return listeners; - } - - listeners = listeners.concat(searchListenerTree(handlers, type, tree[currentType], i+1)); - } - - xTree = tree['*']; - if (xTree) { - // - // If the listener tree will allow any match for this part, - // then recursively explore all branches of the tree - // - searchListenerTree(handlers, type, xTree, i+1); - } - - xxTree = tree['**']; - if(xxTree) { - if(i < typeLength) { - if(xxTree._listeners) { - // If we have a listener on a '**', it will catch all, so add its handler. - searchListenerTree(handlers, type, xxTree, typeLength); - } - - // Build arrays of matching next branches and others. - for(branch in xxTree) { - if(branch !== '_listeners' && xxTree.hasOwnProperty(branch)) { - if(branch === nextType) { - // We know the next element will match, so jump twice. - searchListenerTree(handlers, type, xxTree[branch], i+2); - } else if(branch === currentType) { - // Current node matches, move into the tree. - searchListenerTree(handlers, type, xxTree[branch], i+1); - } else { - isolatedBranch = {}; - isolatedBranch[branch] = xxTree[branch]; - searchListenerTree(handlers, type, { '**': isolatedBranch }, i+1); - } - } - } - } else if(xxTree._listeners) { - // We have reached the end and still on a '**' - searchListenerTree(handlers, type, xxTree, typeLength); - } else if(xxTree['*'] && xxTree['*']._listeners) { - searchListenerTree(handlers, type, xxTree['*'], typeLength); - } - } - - return listeners; - } - - function growListenerTree(type, listener) { - - type = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); - - // - // Looks for two consecutive '**', if so, don't add the event at all. - // - for(var i = 0, len = type.length; i+1 < len; i++) { - if(type[i] === '**' && type[i+1] === '**') { - return; - } - } - - var tree = this.listenerTree; - var name = type.shift(); - - while (name !== undefined) { - - if (!tree[name]) { - tree[name] = {}; - } - - tree = tree[name]; - - if (type.length === 0) { - - if (!tree._listeners) { - tree._listeners = listener; - } - else { - if (typeof tree._listeners === 'function') { - tree._listeners = [tree._listeners]; - } - - tree._listeners.push(listener); - - if ( - !tree._listeners.warned && - this._events.maxListeners > 0 && - tree._listeners.length > this._events.maxListeners - ) { - tree._listeners.warned = true; - logPossibleMemoryLeak.call(this, tree._listeners.length, name); - } - } - return true; - } - name = type.shift(); - } - return true; - } - - // By default EventEmitters will print a warning if more than - // 10 listeners are added to it. This is a useful default which - // helps finding memory leaks. - // - // Obviously not all Emitters should be limited to 10. This function allows - // that to be increased. Set to zero for unlimited. - - EventEmitter.prototype.delimiter = '.'; - - EventEmitter.prototype.setMaxListeners = function(n) { - if (n !== undefined) { - this._events || init.call(this); - this._events.maxListeners = n; - if (!this._conf) this._conf = {}; - this._conf.maxListeners = n; - } - }; - - EventEmitter.prototype.event = ''; - - EventEmitter.prototype.once = function(event, fn) { - this.many(event, 1, fn); - return this; - }; - - EventEmitter.prototype.many = function(event, ttl, fn) { - var self = this; - - if (typeof fn !== 'function') { - throw new Error('many only accepts instances of Function'); - } - - function listener() { - if (--ttl === 0) { - self.off(event, listener); - } - fn.apply(this, arguments); - } - - listener._origin = fn; - - this.on(event, listener); - - return self; - }; - - EventEmitter.prototype.emit = function() { - - this._events || init.call(this); - - var type = arguments[0]; - - if (type === 'newListener' && !this.newListener) { - if (!this._events.newListener) { - return false; - } - } - - var al = arguments.length; - var args,l,i,j; - var handler; - - if (this._all && this._all.length) { - handler = this._all.slice(); - if (al > 3) { - args = new Array(al); - for (j = 0; j < al; j++) args[j] = arguments[j]; - } - - for (i = 0, l = handler.length; i < l; i++) { - this.event = type; - switch (al) { - case 1: - handler[i].call(this, type); - break; - case 2: - handler[i].call(this, type, arguments[1]); - break; - case 3: - handler[i].call(this, type, arguments[1], arguments[2]); - break; - default: - handler[i].apply(this, args); - } - } - } - - if (this.wildcard) { - handler = []; - var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); - searchListenerTree.call(this, handler, ns, this.listenerTree, 0); - } else { - handler = this._events[type]; - if (typeof handler === 'function') { - this.event = type; - switch (al) { - case 1: - handler.call(this); - break; - case 2: - handler.call(this, arguments[1]); - break; - case 3: - handler.call(this, arguments[1], arguments[2]); - break; - default: - args = new Array(al - 1); - for (j = 1; j < al; j++) args[j - 1] = arguments[j]; - handler.apply(this, args); - } - return true; - } else if (handler) { - // need to make copy of handlers because list can change in the middle - // of emit call - handler = handler.slice(); - } - } - - if (handler && handler.length) { - if (al > 3) { - args = new Array(al - 1); - for (j = 1; j < al; j++) args[j - 1] = arguments[j]; - } - for (i = 0, l = handler.length; i < l; i++) { - this.event = type; - switch (al) { - case 1: - handler[i].call(this); - break; - case 2: - handler[i].call(this, arguments[1]); - break; - case 3: - handler[i].call(this, arguments[1], arguments[2]); - break; - default: - handler[i].apply(this, args); - } - } - return true; - } else if (!this._all && type === 'error') { - if (arguments[1] instanceof Error) { - throw arguments[1]; // Unhandled 'error' event - } else { - throw new Error("Uncaught, unspecified 'error' event."); - } - return false; - } - - return !!this._all; - }; - - EventEmitter.prototype.emitAsync = function() { - - this._events || init.call(this); - - var type = arguments[0]; - - if (type === 'newListener' && !this.newListener) { - if (!this._events.newListener) { return Promise.resolve([false]); } - } - - var promises= []; - - var al = arguments.length; - var args,l,i,j; - var handler; - - if (this._all) { - if (al > 3) { - args = new Array(al); - for (j = 1; j < al; j++) args[j] = arguments[j]; - } - for (i = 0, l = this._all.length; i < l; i++) { - this.event = type; - switch (al) { - case 1: - promises.push(this._all[i].call(this, type)); - break; - case 2: - promises.push(this._all[i].call(this, type, arguments[1])); - break; - case 3: - promises.push(this._all[i].call(this, type, arguments[1], arguments[2])); - break; - default: - promises.push(this._all[i].apply(this, args)); - } - } - } - - if (this.wildcard) { - handler = []; - var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); - searchListenerTree.call(this, handler, ns, this.listenerTree, 0); - } else { - handler = this._events[type]; - } - - if (typeof handler === 'function') { - this.event = type; - switch (al) { - case 1: - promises.push(handler.call(this)); - break; - case 2: - promises.push(handler.call(this, arguments[1])); - break; - case 3: - promises.push(handler.call(this, arguments[1], arguments[2])); - break; - default: - args = new Array(al - 1); - for (j = 1; j < al; j++) args[j - 1] = arguments[j]; - promises.push(handler.apply(this, args)); - } - } else if (handler && handler.length) { - if (al > 3) { - args = new Array(al - 1); - for (j = 1; j < al; j++) args[j - 1] = arguments[j]; - } - for (i = 0, l = handler.length; i < l; i++) { - this.event = type; - switch (al) { - case 1: - promises.push(handler[i].call(this)); - break; - case 2: - promises.push(handler[i].call(this, arguments[1])); - break; - case 3: - promises.push(handler[i].call(this, arguments[1], arguments[2])); - break; - default: - promises.push(handler[i].apply(this, args)); - } - } - } else if (!this._all && type === 'error') { - if (arguments[1] instanceof Error) { - return Promise.reject(arguments[1]); // Unhandled 'error' event - } else { - return Promise.reject("Uncaught, unspecified 'error' event."); - } - } - - return Promise.all(promises); - }; - - EventEmitter.prototype.on = function(type, listener) { - if (typeof type === 'function') { - this.onAny(type); - return this; - } - - if (typeof listener !== 'function') { - throw new Error('on only accepts instances of Function'); - } - this._events || init.call(this); - - // To avoid recursion in the case that type == "newListeners"! Before - // adding it to the listeners, first emit "newListeners". - this.emit('newListener', type, listener); - - if (this.wildcard) { - growListenerTree.call(this, type, listener); - return this; - } - - if (!this._events[type]) { - // Optimize the case of one listener. Don't need the extra array object. - this._events[type] = listener; - } - else { - if (typeof this._events[type] === 'function') { - // Change to array. - this._events[type] = [this._events[type]]; - } - - // If we've already got an array, just append. - this._events[type].push(listener); - - // Check for listener leak - if ( - !this._events[type].warned && - this._events.maxListeners > 0 && - this._events[type].length > this._events.maxListeners - ) { - this._events[type].warned = true; - logPossibleMemoryLeak.call(this, this._events[type].length, type); - } - } - - return this; - }; - - EventEmitter.prototype.onAny = function(fn) { - if (typeof fn !== 'function') { - throw new Error('onAny only accepts instances of Function'); - } - - if (!this._all) { - this._all = []; - } - - // Add the function to the event listener collection. - this._all.push(fn); - return this; - }; - - EventEmitter.prototype.addListener = EventEmitter.prototype.on; - - EventEmitter.prototype.off = function(type, listener) { - if (typeof listener !== 'function') { - throw new Error('removeListener only takes instances of Function'); - } - - var handlers,leafs=[]; - - if(this.wildcard) { - var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); - leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0); - } - else { - // does not use listeners(), so no side effect of creating _events[type] - if (!this._events[type]) return this; - handlers = this._events[type]; - leafs.push({_listeners:handlers}); - } - - for (var iLeaf=0; iLeaf 0) { - recursivelyGarbageCollect(root[key]); - } - if (Object.keys(obj).length === 0) { - delete root[key]; - } - } - } - recursivelyGarbageCollect(this.listenerTree); - - return this; - }; - - EventEmitter.prototype.offAny = function(fn) { - var i = 0, l = 0, fns; - if (fn && this._all && this._all.length > 0) { - fns = this._all; - for(i = 0, l = fns.length; i < l; i++) { - if(fn === fns[i]) { - fns.splice(i, 1); - this.emit("removeListenerAny", fn); - return this; - } - } - } else { - fns = this._all; - for(i = 0, l = fns.length; i < l; i++) - this.emit("removeListenerAny", fns[i]); - this._all = []; - } - return this; - }; - - EventEmitter.prototype.removeListener = EventEmitter.prototype.off; - - EventEmitter.prototype.removeAllListeners = function(type) { - if (arguments.length === 0) { - !this._events || init.call(this); - return this; - } - - if (this.wildcard) { - var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); - var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0); - - for (var iLeaf=0; iLeaf 0) { + makeUl(liElem, children[i]); + divElem.addEventListener('click', that.hide.bind(that)); + divElem.addEventListener('touchstart', that.hide.bind(that)); + } else { + divElem.addEventListener('click', emitMenuSelect.bind(that, children[i])); + divElem.addEventListener('touchstart', emitMenuSelect.bind(that, children[i])); + divElem.className = 'default-interactive-marker-menu-entry'; + } + } + + } + + // construct DOM element + makeUl(this.menuDomElem, allMenus[0]); +}; + +/** + * Shoe the menu DOM element. + * + * @param control - the control for the menu + * @param event - the event that caused this + */ +ROS3D.InteractiveMarkerMenu.prototype.show = function(control, event) { + if (event && event.preventDefault) { + event.preventDefault(); + } + + this.controlName = control.name; + + // position it on the click + if (event.domEvent.changedTouches !== undefined) { + // touch click + this.menuDomElem.style.left = event.domEvent.changedTouches[0].pageX + 'px'; + this.menuDomElem.style.top = event.domEvent.changedTouches[0].pageY + 'px'; + } else { + // mouse click + this.menuDomElem.style.left = event.domEvent.clientX + 'px'; + this.menuDomElem.style.top = event.domEvent.clientY + 'px'; + } + document.body.appendChild(this.overlayDomElem); + document.body.appendChild(this.menuDomElem); +}; + +/** + * Hide the menu DOM element. + * + * @param event (optional) - the event that caused this + */ +ROS3D.InteractiveMarkerMenu.prototype.hide = function(event) { + if (event && event.preventDefault) { + event.preventDefault(); + } + + document.body.removeChild(this.overlayDomElem); + document.body.removeChild(this.menuDomElem); +}; + +THREE.EventDispatcher.prototype.apply( ROS3D.InteractiveMarkerMenu.prototype ); /** - * @author Jihoon Lee - jihoonlee.in@gmail.com * @author Russell Toris - rctoris@wpi.edu */ -class SceneNode extends THREE$1.Object3D { +/** + * An OccupancyGrid can convert a ROS occupancy grid message into a THREE object. + * + * @constructor + * @param options - object with following keys: + * + * * message - the occupancy grid message + */ +ROS3D.OccupancyGrid = function(options) { + options = options || {}; + var message = options.message; - /** - * A SceneNode can be used to keep track of a 3D object with respect to a ROS frame within a scene. - * - * @constructor - * @param options - object with following keys: - * - * * tfClient - a handle to the TF client - * * frameID - the frame ID this object belongs to - * * pose (optional) - the pose associated with this object - * * object - the THREE 3D object to be rendered - */ - constructor(options) { - super(); - options = options || {}; - var that = this; - this.tfClient = options.tfClient; - this.frameID = options.frameID; - var object = options.object; - this.pose = options.pose || new ROSLIB.Pose(); + // create the geometry + var width = message.info.width; + var height = message.info.height; + var geom = new THREE.PlaneGeometry(width, height); - // Do not render this object until we receive a TF update - this.visible = false; + // internal drawing canvas + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + var context = canvas.getContext('2d'); + // create the color material + var imageData = context.createImageData(width, height); + for ( var row = 0; row < height; row++) { + for ( var col = 0; col < width; col++) { + // determine the index into the map data + var mapI = col + ((height - row - 1) * width); + // determine the value + var data = message.data[mapI]; + var val; + if (data === 100) { + val = 0; + } else if (data === 0) { + val = 255; + } else { + val = 127; + } - // add the model - this.add(object); + // determine the index into the image data array + var i = (col + (row * width)) * 4; + // r + imageData.data[i] = val; + // g + imageData.data[++i] = val; + // b + imageData.data[++i] = val; + // a + imageData.data[++i] = 255; + } + } + context.putImageData(imageData, 0, 0); - // set the inital pose - this.updatePose(this.pose); + var texture = new THREE.Texture(canvas); + texture.needsUpdate = true; + var material = new THREE.MeshBasicMaterial({ + map : texture + }); + material.side = THREE.DoubleSide; - // save the TF handler so we can remove it later - this.tfUpdate = function(msg) { + // create the mesh + THREE.Mesh.call(this, geom, material); + // move the map so the corner is at 0, 0 + this.position.x = (width * message.info.resolution) / 2; + this.position.y = (height * message.info.resolution) / 2; + this.scale.x = message.info.resolution; + this.scale.y = message.info.resolution; +}; +ROS3D.OccupancyGrid.prototype.__proto__ = THREE.Mesh.prototype; - // apply the transform - var tf = new ROSLIB.Transform(msg); - var poseTransformed = new ROSLIB.Pose(that.pose); - poseTransformed.applyTransform(tf); +/** + * @author Russell Toris - rctoris@wpi.edu + */ - // update the world - that.updatePose(poseTransformed); - that.visible = true; - }; +/** + * An occupancy grid client that listens to a given map topic. + * + * Emits the following events: + * + * * 'change' - there was an update or change in the marker + * + * @constructor + * @param options - object with following keys: + * + * * ros - the ROSLIB.Ros connection handle + * * topic (optional) - the map topic to listen to + * * continuous (optional) - if the map should be continuously loaded (e.g., for SLAM) + * * tfClient (optional) - the TF client handle to use for a scene node + * * rootObject (optional) - the root object to add this marker to + */ +ROS3D.OccupancyGridClient = function(options) { + var that = this; + options = options || {}; + var ros = options.ros; + var topic = options.topic || '/map'; + this.continuous = options.continuous; + this.tfClient = options.tfClient; + this.rootObject = options.rootObject || new THREE.Object3D(); - // listen for TF updates - this.tfClient.subscribe(this.frameID, this.tfUpdate); - }; + // current grid that is displayed + this.currentGrid = null; - /** - * Set the pose of the associated model. - * - * @param pose - the pose to update with - */ - updatePose(pose) { - this.position.set( pose.position.x, pose.position.y, pose.position.z ); - this.quaternion.set(pose.orientation.x, pose.orientation.y, - pose.orientation.z, pose.orientation.w); - this.updateMatrixWorld(true); - }; + // subscribe to the topic + var rosTopic = new ROSLIB.Topic({ + ros : ros, + name : topic, + messageType : 'nav_msgs/OccupancyGrid', + compression : 'png' + }); + rosTopic.subscribe(function(message) { + // check for an old map + if (that.currentGrid) { + that.rootObject.remove(that.currentGrid); + } - unsubscribeTf() { - this.tfClient.unsubscribe(this.frameID, this.tfUpdate); - }; -} + var newGrid = new ROS3D.OccupancyGrid({ + message : message + }); + + // check if we care about the scene + if (that.tfClient) { + that.currentGrid = new ROS3D.SceneNode({ + frameID : message.header.frame_id, + tfClient : that.tfClient, + object : newGrid, + pose : message.info.origin + }); + } else { + that.currentGrid = newGrid; + } + + that.rootObject.add(that.currentGrid); + + that.emit('change'); + + // check if we should unsubscribe + if (!that.continuous) { + rosTopic.unsubscribe(); + } + }); +}; +ROS3D.OccupancyGridClient.prototype.__proto__ = EventEmitter2.prototype; + +/** + * @author David Gossow - dgossow@willowgarage.com + * @author Russell Toris - rctoris@wpi.edu + */ + +/** + * A Marker can convert a ROS marker message into a THREE object. + * + * @constructor + * @param options - object with following keys: + * + * * path - the base path or URL for any mesh files that will be loaded for this marker + * * message - the marker message + * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER + * ROS3D.COLLADA_LOADER_2) -- defaults to ROS3D.COLLADA_LOADER_2 + */ +ROS3D.Marker = function(options) { + options = options || {}; + var path = options.path || '/'; + var message = options.message; + var loader = options.loader || ROS3D.COLLADA_LOADER_2; + + // check for a trailing '/' + if (path.substr(path.length - 1) !== '/') { + path += '/'; + } + + THREE.Object3D.call(this); + + if(message.scale) { + this.msgScale = [message.scale.x, message.scale.y, message.scale.z]; + } + else { + this.msgScale = [1,1,1]; + } + this.msgColor = message.color; + this.msgMesh = undefined; + + // set the pose and get the color + this.setPose(message.pose); + var colorMaterial = ROS3D.makeColorMaterial(this.msgColor.r, + this.msgColor.g, this.msgColor.b, this.msgColor.a); + + // create the object based on the type + switch (message.type) { + case ROS3D.MARKER_ARROW: + // get the sizes for the arrow + var len = message.scale.x; + var headLength = len * 0.23; + var headDiameter = message.scale.y; + var shaftDiameter = headDiameter * 0.5; + + // determine the points + var direction, p1 = null; + if (message.points.length === 2) { + p1 = new THREE.Vector3(message.points[0].x, message.points[0].y, message.points[0].z); + var p2 = new THREE.Vector3(message.points[1].x, message.points[1].y, message.points[1].z); + direction = p1.clone().negate().add(p2); + // direction = p2 - p1; + len = direction.length(); + headDiameter = message.scale.y; + shaftDiameter = message.scale.x; + + if (message.scale.z !== 0.0) { + headLength = message.scale.z; + } + } + + // add the marker + this.add(new ROS3D.Arrow({ + direction : direction, + origin : p1, + length : len, + headLength : headLength, + shaftDiameter : shaftDiameter, + headDiameter : headDiameter, + material : colorMaterial + })); + break; + case ROS3D.MARKER_CUBE: + // set the cube dimensions + var cubeGeom = new THREE.CubeGeometry(message.scale.x, message.scale.y, message.scale.z); + this.add(new THREE.Mesh(cubeGeom, colorMaterial)); + break; + case ROS3D.MARKER_SPHERE: + // set the sphere dimensions + var sphereGeom = new THREE.SphereGeometry(0.5); + var sphereMesh = new THREE.Mesh(sphereGeom, colorMaterial); + sphereMesh.scale.x = message.scale.x; + sphereMesh.scale.y = message.scale.y; + sphereMesh.scale.z = message.scale.z; + this.add(sphereMesh); + break; + case ROS3D.MARKER_CYLINDER: + // set the cylinder dimensions + var cylinderGeom = new THREE.CylinderGeometry(0.5, 0.5, 1, 16, 1, false); + var cylinderMesh = new THREE.Mesh(cylinderGeom, colorMaterial); + cylinderMesh.quaternion.setFromAxisAngle(new THREE.Vector3(1, 0, 0), Math.PI * 0.5); + cylinderMesh.scale = new THREE.Vector3(message.scale.x, message.scale.z, message.scale.y); + this.add(cylinderMesh); + break; + case ROS3D.MARKER_LINE_STRIP: + var lineStripGeom = new THREE.Geometry(); + var lineStripMaterial = new THREE.LineBasicMaterial({ + size : message.scale.x + }); + + // add the points + var j; + for ( j = 0; j < message.points.length; j++) { + var pt = new THREE.Vector3(); + pt.x = message.points[j].x; + pt.y = message.points[j].y; + pt.z = message.points[j].z; + lineStripGeom.vertices.push(pt); + } + + // determine the colors for each + if (message.colors.length === message.points.length) { + lineStripMaterial.vertexColors = true; + for ( j = 0; j < message.points.length; j++) { + var clr = new THREE.Color(); + clr.setRGB(message.colors[j].r, message.colors[j].g, message.colors[j].b); + lineStripGeom.colors.push(clr); + } + } else { + lineStripMaterial.color.setRGB(message.color.r, message.color.g, message.color.b); + } + + // add the line + this.add(new THREE.Line(lineStripGeom, lineStripMaterial)); + break; + case ROS3D.MARKER_LINE_LIST: + var lineListGeom = new THREE.Geometry(); + var lineListMaterial = new THREE.LineBasicMaterial({ + size : message.scale.x + }); + + // add the points + var k; + for ( k = 0; k < message.points.length; k++) { + var v = new THREE.Vector3(); + v.x = message.points[k].x; + v.y = message.points[k].y; + v.z = message.points[k].z; + lineListGeom.vertices.push(v); + } + + // determine the colors for each + if (message.colors.length === message.points.length) { + lineListMaterial.vertexColors = true; + for ( k = 0; k < message.points.length; k++) { + var c = new THREE.Color(); + c.setRGB(message.colors[k].r, message.colors[k].g, message.colors[k].b); + lineListGeom.colors.push(c); + } + } else { + lineListMaterial.color.setRGB(message.color.r, message.color.g, message.color.b); + } + + // add the line + this.add(new THREE.Line(lineListGeom, lineListMaterial,THREE.LinePieces)); + break; + case ROS3D.MARKER_CUBE_LIST: + // holds the main object + var object = new THREE.Object3D(); + + // check if custom colors should be used + var numPoints = message.points.length; + var createColors = (numPoints === message.colors.length); + // do not render giant lists + var stepSize = Math.ceil(numPoints / 1250); + + // add the points + var p, cube, curColor, newMesh; + for (p = 0; p < numPoints; p+=stepSize) { + cube = new THREE.CubeGeometry(message.scale.x, message.scale.y, message.scale.z); + + // check the color + if(createColors) { + curColor = ROS3D.makeColorMaterial(message.colors[p].r, message.colors[p].g, message.colors[p].b, message.colors[p].a); + } else { + curColor = colorMaterial; + } + + newMesh = new THREE.Mesh(cube, curColor); + newMesh.position.x = message.points[p].x; + newMesh.position.y = message.points[p].y; + newMesh.position.z = message.points[p].z; + object.add(newMesh); + } + + this.add(object); + break; + case ROS3D.MARKER_SPHERE_LIST: + case ROS3D.MARKER_POINTS: + // for now, use a particle system for the lists + var geometry = new THREE.Geometry(); + var material = new THREE.ParticleBasicMaterial({ + size : message.scale.x + }); + + // add the points + var i; + for ( i = 0; i < message.points.length; i++) { + var vertex = new THREE.Vector3(); + vertex.x = message.points[i].x; + vertex.y = message.points[i].y; + vertex.z = message.points[i].z; + geometry.vertices.push(vertex); + } + + // determine the colors for each + if (message.colors.length === message.points.length) { + material.vertexColors = true; + for ( i = 0; i < message.points.length; i++) { + var color = new THREE.Color(); + color.setRGB(message.colors[i].r, message.colors[i].g, message.colors[i].b); + geometry.colors.push(color); + } + } else { + material.color.setRGB(message.color.r, message.color.g, message.color.b); + } + + // add the particle system + this.add(new THREE.ParticleSystem(geometry, material)); + break; + case ROS3D.MARKER_TEXT_VIEW_FACING: + // only work on non-empty text + if (message.text.length > 0) { + // Use a THREE.Sprite to always be view-facing + // ( code from http://stackoverflow.com/a/27348780 ) + var textColor = this.msgColor; + + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + var textHeight = 100; + var fontString = 'normal ' + textHeight + 'px sans-serif'; + context.font = fontString; + var metrics = context.measureText( message.text ); + var textWidth = metrics.width; + + canvas.width = textWidth; + // To account for overhang (like the letter 'g'), make the canvas bigger + // The non-text portion is transparent anyway + canvas.height = 1.5 * textHeight; + + // this does need to be set again + context.font = fontString; + context.fillStyle = 'rgba(' + + textColor.r + ', ' + + textColor.g + ', ' + + textColor.b + ', ' + + textColor.a + ')'; + context.textAlign = 'left'; + context.textBaseline = 'middle'; + context.fillText( message.text, 0, canvas.height/2); + + var texture = new THREE.Texture(canvas); + texture.needsUpdate = true; + + var spriteMaterial = new THREE.SpriteMaterial({ + map: texture, + // NOTE: This is needed for THREE.js r61, unused in r70 + useScreenCoordinates: false }); + var sprite = new THREE.Sprite( spriteMaterial ); + var textSize = message.scale.x; + sprite.scale.set(textWidth / canvas.height * textSize, textSize, 1); + + this.add(sprite); } + break; + case ROS3D.MARKER_MESH_RESOURCE: + // load and add the mesh + var meshColorMaterial = null; + if(message.color.r !== 0 || message.color.g !== 0 || + message.color.b !== 0 || message.color.a !== 0) { + meshColorMaterial = colorMaterial; + } + this.msgMesh = message.mesh_resource.substr(10); + var meshResource = new ROS3D.MeshResource({ + path : path, + resource : this.msgMesh, + material : meshColorMaterial, + loader : loader + }); + this.add(meshResource); + break; + case ROS3D.MARKER_TRIANGLE_LIST: + // create the list of triangles + var tri = new ROS3D.TriangleList({ + material : colorMaterial, + vertices : message.points, + colors : message.colors + }); + tri.scale = new THREE.Vector3(message.scale.x, message.scale.y, message.scale.z); + this.add(tri); + break; + default: + console.error('Currently unsupported marker type: ' + message.type); + break; + } +}; +ROS3D.Marker.prototype.__proto__ = THREE.Object3D.prototype; + +/** + * Set the pose of this marker to the given values. + * + * @param pose - the pose to set for this marker + */ +ROS3D.Marker.prototype.setPose = function(pose) { + // set position information + this.position.x = pose.position.x; + this.position.y = pose.position.y; + this.position.z = pose.position.z; + + // set the rotation + this.quaternion = new THREE.Quaternion(pose.orientation.x, pose.orientation.y, + pose.orientation.z, pose.orientation.w); + this.quaternion.normalize(); + + // update the world + this.updateMatrixWorld(); +}; + +/** + * Update this marker. + * + * @param message - the marker message + * @return true on success otherwhise false is returned + */ +ROS3D.Marker.prototype.update = function(message) { + // set the pose and get the color + this.setPose(message.pose); + + // Update color + if(message.color.r !== this.msgColor.r || + message.color.g !== this.msgColor.g || + message.color.b !== this.msgColor.b || + message.color.a !== this.msgColor.a) + { + var colorMaterial = ROS3D.makeColorMaterial( + message.color.r, message.color.g, + message.color.b, message.color.a); + + switch (message.type) { + case ROS3D.MARKER_LINE_STRIP: + case ROS3D.MARKER_LINE_LIST: + case ROS3D.MARKER_POINTS: + break; + case ROS3D.MARKER_ARROW: + case ROS3D.MARKER_CUBE: + case ROS3D.MARKER_SPHERE: + case ROS3D.MARKER_CYLINDER: + case ROS3D.MARKER_TRIANGLE_LIST: + case ROS3D.MARKER_TEXT_VIEW_FACING: + this.traverse (function (child){ + if (child instanceof THREE.Mesh) { + child.material = colorMaterial; + } + }); + break; + case ROS3D.MARKER_MESH_RESOURCE: + var meshColorMaterial = null; + if(message.color.r !== 0 || message.color.g !== 0 || + message.color.b !== 0 || message.color.a !== 0) { + meshColorMaterial = this.colorMaterial; + } + this.traverse (function (child){ + if (child instanceof THREE.Mesh) { + child.material = meshColorMaterial; + } + }); + break; + case ROS3D.MARKER_CUBE_LIST: + case ROS3D.MARKER_SPHERE_LIST: + // TODO Support to update color for MARKER_CUBE_LIST & MARKER_SPHERE_LIST + return false; + default: + return false; + } + + this.msgColor = message.color; + } + + // Update geometry + var scaleChanged = + Math.abs(this.msgScale[0] - message.scale.x) > 1.0e-6 || + Math.abs(this.msgScale[1] - message.scale.y) > 1.0e-6 || + Math.abs(this.msgScale[2] - message.scale.z) > 1.0e-6; + this.msgScale = [message.scale.x, message.scale.y, message.scale.z]; + + switch (message.type) { + case ROS3D.MARKER_CUBE: + case ROS3D.MARKER_SPHERE: + case ROS3D.MARKER_CYLINDER: + if(scaleChanged) { + return false; + } + break; + case ROS3D.MARKER_TEXT_VIEW_FACING: + if(scaleChanged || this.text !== message.text) { + return false; + } + break; + case ROS3D.MARKER_MESH_RESOURCE: + var meshResource = message.mesh_resource.substr(10); + if(meshResource !== this.msgMesh) { + return false; + } + if(scaleChanged) { + return false; + } + break; + case ROS3D.MARKER_ARROW: + case ROS3D.MARKER_LINE_STRIP: + case ROS3D.MARKER_LINE_LIST: + case ROS3D.MARKER_CUBE_LIST: + case ROS3D.MARKER_SPHERE_LIST: + case ROS3D.MARKER_POINTS: + case ROS3D.MARKER_TRIANGLE_LIST: + // TODO: Check if geometry changed + return false; + default: + break; + } + + return true; +}; + +/* + * Free memory of elements in this marker. + */ +ROS3D.Marker.prototype.dispose = function() { + this.children.forEach(function(element) { + if (element instanceof ROS3D.MeshResource) { + element.children.forEach(function(scene) { + if (scene.material !== undefined) { + scene.material.dispose(); + } + scene.children.forEach(function(mesh) { + if (mesh.geometry !== undefined) { + mesh.geometry.dispose(); + } + if (mesh.material !== undefined) { + mesh.material.dispose(); + } + scene.remove(mesh); + }); + element.remove(scene); + }); + } else { + if (element.geometry !== undefined) { + element.geometry.dispose(); + } + if (element.material !== undefined) { + element.material.dispose(); + } + } + element.parent.remove(element); + }); +}; /** * @author Russell Toris - rctoris@wpi.edu * @author Nils Berg - berg.nils@gmail.com */ -class MarkerArrayClient extends eventemitter2 { +/** + * A MarkerArray client that listens to a given topic. + * + * Emits the following events: + * + * * 'change' - there was an update or change in the MarkerArray + * + * @constructor + * @param options - object with following keys: + * + * * ros - the ROSLIB.Ros connection handle + * * topic - the marker topic to listen to + * * tfClient - the TF client handle to use + * * rootObject (optional) - the root object to add the markers to + * * path (optional) - the base path to any meshes that will be loaded + * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER + * ROS3D.COLLADA_LOADER_2) -- defaults to ROS3D.COLLADA_LOADER_2 + */ +ROS3D.MarkerArrayClient = function(options) { + var that = this; + options = options || {}; + var ros = options.ros; + var topic = options.topic; + this.tfClient = options.tfClient; + this.rootObject = options.rootObject || new THREE.Object3D(); + this.path = options.path || '/'; + this.loader = options.loader || ROS3D.COLLADA_LOADER_2; - /** - * A MarkerArray client that listens to a given topic. - * - * Emits the following events: - * - * * 'change' - there was an update or change in the MarkerArray - * - * @constructor - * @param options - object with following keys: - * - * * ros - the ROSLIB.Ros connection handle - * * topic - the marker topic to listen to - * * tfClient - the TF client handle to use - * * rootObject (optional) - the root object to add the markers to - * * path (optional) - the base path to any meshes that will be loaded - */ - constructor(options) { - super(); - options = options || {}; - this.ros = options.ros; - this.topicName = options.topic; - this.tfClient = options.tfClient; - this.rootObject = options.rootObject || new THREE$1.Object3D(); - this.path = options.path || '/'; + // Markers that are displayed (Map ns+id--Marker) + this.markers = {}; - // Markers that are displayed (Map ns+id--Marker) - this.markers = {}; - this.rosTopic = undefined; + // subscribe to MarkerArray topic + var arrayTopic = new ROSLIB.Topic({ + ros : ros, + name : topic, + messageType : 'visualization_msgs/MarkerArray', + compression : 'png' + }); + + arrayTopic.subscribe(function(arrayMessage) { - this.subscribe(); - }; - - subscribe(){ - this.unsubscribe(); - - // subscribe to MarkerArray topic - this.rosTopic = new ROSLIB.Topic({ - ros : this.ros, - name : this.topicName, - messageType : 'visualization_msgs/MarkerArray', - compression : 'png' - }); - this.rosTopic.subscribe(this.processMessage.bind(this)); - }; - - processMessage(arrayMessage){ arrayMessage.markers.forEach(function(message) { if(message.action === 0) { var updated = false; - if(message.ns + message.id in this.markers) { // "MODIFY" - updated = this.markers[message.ns + message.id].children[0].update(message); + if(message.ns + message.id in that.markers) { // "MODIFY" + updated = that.markers[message.ns + message.id].children[0].update(message); if(!updated) { // "REMOVE" - this.markers[message.ns + message.id].unsubscribeTf(); - this.rootObject.remove(this.markers[message.ns + message.id]); + that.rootObject.remove(that.markers[message.ns + message.id]); } } if(!updated) { // "ADD" - var newMarker = new Marker({ + var newMarker = new ROS3D.Marker({ message : message, - path : this.path, + path : that.path, + loader : that.loader }); - this.markers[message.ns + message.id] = new SceneNode({ + that.markers[message.ns + message.id] = new ROS3D.SceneNode({ frameID : message.header.frame_id, - tfClient : this.tfClient, + tfClient : that.tfClient, object : newMarker }); - this.rootObject.add(this.markers[message.ns + message.id]); + that.rootObject.add(that.markers[message.ns + message.id]); } } else if(message.action === 1) { // "DEPRECATED" console.warn('Received marker message with deprecated action identifier "1"'); } else if(message.action === 2) { // "DELETE" - this.markers[message.ns + message.id].unsubscribeTf(); - this.rootObject.remove(this.markers[message.ns + message.id]); - delete this.markers[message.ns + message.id]; + that.rootObject.remove(that.markers[message.ns + message.id]); + delete that.markers[message.ns + message.id]; } else if(message.action === 3) { // "DELETE ALL" - for (var m in this.markers){ - this.markers[m].unsubscribeTf(); - this.rootObject.remove(this.markers[m]); + for (var m in that.markers){ + that.rootObject.remove(m); } - this.markers = {}; + that.markers = {}; } else { console.warn('Received marker message with unknown action identifier "'+message.action+'"'); } - }.bind(this)); - - this.emit('change'); - }; - - unsubscribe(){ - if(this.rosTopic){ - this.rosTopic.unsubscribe(); - } - }; -} + }); + + that.emit('change'); + }); +}; +ROS3D.MarkerArrayClient.prototype.__proto__ = EventEmitter2.prototype; /** * @author Russell Toris - rctoris@wpi.edu */ -class MarkerClient extends eventemitter2 { +/** + * A marker client that listens to a given marker topic. + * + * Emits the following events: + * + * * 'change' - there was an update or change in the marker + * + * @constructor + * @param options - object with following keys: + * + * * ros - the ROSLIB.Ros connection handle + * * topic - the marker topic to listen to + * * tfClient - the TF client handle to use + * * rootObject (optional) - the root object to add this marker to + * * path (optional) - the base path to any meshes that will be loaded + * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER + * ROS3D.COLLADA_LOADER_2) -- defaults to ROS3D.COLLADA_LOADER_2 + */ +ROS3D.MarkerClient = function(options) { + var that = this; + options = options || {}; + var ros = options.ros; + var topic = options.topic; + this.tfClient = options.tfClient; + this.rootObject = options.rootObject || new THREE.Object3D(); + this.path = options.path || '/'; + this.loader = options.loader || ROS3D.COLLADA_LOADER_2; - /** - * A marker client that listens to a given marker topic. - * - * Emits the following events: - * - * * 'change' - there was an update or change in the marker - * - * @constructor - * @param options - object with following keys: - * - * * ros - the ROSLIB.Ros connection handle - * * topic - the marker topic to listen to - * * tfClient - the TF client handle to use - * * rootObject (optional) - the root object to add this marker to - * * path (optional) - the base path to any meshes that will be loaded - */ - constructor(options) { - super(); - options = options || {}; - this.ros = options.ros; - this.topicName = options.topic; - this.tfClient = options.tfClient; - this.rootObject = options.rootObject || new THREE$1.Object3D(); - this.path = options.path || '/'; + // Markers that are displayed (Map ns+id--Marker) + this.markers = {}; - // Markers that are displayed (Map ns+id--Marker) - this.markers = {}; - this.rosTopic = undefined; + // subscribe to the topic + var rosTopic = new ROSLIB.Topic({ + ros : ros, + name : topic, + messageType : 'visualization_msgs/Marker', + compression : 'png' + }); + rosTopic.subscribe(function(message) { - this.subscribe(); - }; - - unsubscribe(){ - if(this.rosTopic){ - this.rosTopic.unsubscribe(); - } - }; - - subscribe(){ - this.unsubscribe(); - - // subscribe to the topic - this.rosTopic = new ROSLIB.Topic({ - ros : this.ros, - name : this.topicName, - messageType : 'visualization_msgs/Marker', - compression : 'png' - }); - this.rosTopic.subscribe(this.processMessage.bind(this)); - }; - - processMessage(message){ - var newMarker = new Marker({ + var newMarker = new ROS3D.Marker({ message : message, - path : this.path, + path : that.path, + loader : that.loader }); // remove old marker from Three.Object3D children buffer - var oldNode = this.markers[message.ns + message.id]; - if (oldNode) { - oldNode.unsubscribeTf(); - this.rootObject.remove(oldNode); - } + that.rootObject.remove(that.markers[message.ns + message.id]); - this.markers[message.ns + message.id] = new SceneNode({ + that.markers[message.ns + message.id] = new ROS3D.SceneNode({ frameID : message.header.frame_id, - tfClient : this.tfClient, + tfClient : that.tfClient, object : newMarker }); - this.rootObject.add(this.markers[message.ns + message.id]); + that.rootObject.add(that.markers[message.ns + message.id]); - this.emit('change'); - }; -} - -/** - * @author Jihoon Lee - lee@magazino.eu - */ - -class Arrow2 extends THREE$1.ArrowHelper { - - /** - * A Arrow is a THREE object that can be used to display an arrow model using ArrowHelper - * - * @constructor - * @param options - object with following keys: - * - * * origin (optional) - the origin of the arrow - * * direction (optional) - the direction vector of the arrow - * * length (optional) - the length of the arrow - * * headLength (optional) - the head length of the arrow - * * shaftDiameter (optional) - the shaft diameter of the arrow - * * headDiameter (optional) - the head diameter of the arrow - * * material (optional) - the material to use for this arrow - */ - constructor(options) { - options = options || {}; - var origin = options.origin || new THREE$1.Vector3(0, 0, 0); - var direction = options.direction || new THREE$1.Vector3(1, 0, 0); - var length = options.length || 1; - var headLength = options.headLength || 0.2; - var shaftDiameter = options.shaftDiameter || 0.05; - var headDiameter = options.headDiameter || 0.1; - var material = options.material || new THREE$1.MeshBasicMaterial(); - - super(direction, origin, length, 0xff0000); - - }; - - - /* - * Free memory of elements in this object. - */ - dispose() { - if (this.line !== undefined) { - this.line.material.dispose(); - this.line.geometry.dispose(); - } - if (this.cone!== undefined) { - this.cone.material.dispose(); - this.cone.geometry.dispose(); - } - }; - - /* - setLength ( length, headLength, headWidth ) { - if ( headLength === undefined ) { - headLength = 0.2 * length; - } - if ( headWidth === undefined ) { - headWidth = 0.2 * headLength; - } - - this.line.scale.set( 1, Math.max( 0, length), 1 ); - this.line.updateMatrix(); - - this.cone.scale.set( headWidth, headLength, headWidth ); - this.cone.position.y = length; - this.cone.updateMatrix(); - - }; - */ -} + that.emit('change'); + }); +}; +ROS3D.MarkerClient.prototype.__proto__ = EventEmitter2.prototype; /** * @author David Gossow - dgossow@willowgarage.com */ -class Axes extends THREE$1.Object3D { +/** + * A Arrow is a THREE object that can be used to display an arrow model. + * + * @constructor + * @param options - object with following keys: + * + * * origin (optional) - the origin of the arrow + * * direction (optional) - the direction vector of the arrow + * * length (optional) - the length of the arrow + * * headLength (optional) - the head length of the arrow + * * shaftDiameter (optional) - the shaft diameter of the arrow + * * headDiameter (optional) - the head diameter of the arrow + * * material (optional) - the material to use for this arrow + */ +ROS3D.Arrow = function(options) { + options = options || {}; + var origin = options.origin || new THREE.Vector3(0, 0, 0); + var direction = options.direction || new THREE.Vector3(1, 0, 0); + var length = options.length || 1; + var headLength = options.headLength || 0.2; + var shaftDiameter = options.shaftDiameter || 0.05; + var headDiameter = options.headDiameter || 0.1; + var material = options.material || new THREE.MeshBasicMaterial(); + + var shaftLength = length - headLength; + + // create and merge geometry + var geometry = new THREE.CylinderGeometry(shaftDiameter * 0.5, shaftDiameter * 0.5, shaftLength, + 12, 1); + var m = new THREE.Matrix4(); + m.setPosition(new THREE.Vector3(0, shaftLength * 0.5, 0)); + geometry.applyMatrix(m); + + // create the head + var coneGeometry = new THREE.CylinderGeometry(0, headDiameter * 0.5, headLength, 12, 1); + m.setPosition(new THREE.Vector3(0, shaftLength + (headLength * 0.5), 0)); + coneGeometry.applyMatrix(m); + + // put the arrow together + THREE.GeometryUtils.merge(geometry, coneGeometry); + + THREE.Mesh.call(this, geometry, material); + + this.position = origin; + this.setDirection(direction); +}; +ROS3D.Arrow.prototype.__proto__ = THREE.Mesh.prototype; + +/** + * Set the direction of this arrow to that of the given vector. + * + * @param direction - the direction to set this arrow + */ +ROS3D.Arrow.prototype.setDirection = function(direction) { + var axis = new THREE.Vector3(0, 1, 0).cross(direction); + var radians = Math.acos(new THREE.Vector3(0, 1, 0).dot(direction.clone().normalize())); + this.matrix = new THREE.Matrix4().makeRotationAxis(axis.normalize(), radians); + this.rotation.setFromRotationMatrix(this.matrix, this.rotation.order); +}; + +/** + * Set this arrow to be the given length. + * + * @param length - the new length of the arrow + */ +ROS3D.Arrow.prototype.setLength = function(length) { + this.scale.set(length, length, length); +}; + +/** + * Set the color of this arrow to the given hex value. + * + * @param hex - the hex value of the color to use + */ +ROS3D.Arrow.prototype.setColor = function(hex) { + this.line.material.color.setHex(hex); + this.cone.material.color.setHex(hex); +}; + +/** + * @author David Gossow - dgossow@willowgarage.com + */ + +/** + * An Axes object can be used to display the axis of a particular coordinate frame. + * + * @constructor + * @param options - object with following keys: + * + * * shaftRadius (optional) - the radius of the shaft to render + * * headRadius (optional) - the radius of the head to render + * * headLength (optional) - the length of the head to render + */ +ROS3D.Axes = function(options) { + var that = this; + options = options || {}; + var shaftRadius = options.shaftRadius || 0.008; + var headRadius = options.headRadius || 0.023; + var headLength = options.headLength || 0.1; + + THREE.Object3D.call(this); + + // create the cylinders for the objects + this.lineGeom = new THREE.CylinderGeometry(shaftRadius, shaftRadius, 1.0 - headLength); + this.headGeom = new THREE.CylinderGeometry(0, headRadius, headLength); /** - * An Axes object can be used to display the axis of a particular coordinate frame. + * Adds an axis marker to this axes object. * - * @constructor - * @param options - object with following keys: - * - * * shaftRadius (optional) - the radius of the shaft to render - * * headRadius (optional) - the radius of the head to render - * * headLength (optional) - the length of the head to render + * @param axis - the 3D vector representing the axis to add */ - constructor(options) { - super(); - var that = this; - options = options || {}; - var shaftRadius = options.shaftRadius || 0.008; - var headRadius = options.headRadius || 0.023; - var headLength = options.headLength || 0.1; + function addAxis(axis) { + // set the color of the axis + var color = new THREE.Color(); + color.setRGB(axis.x, axis.y, axis.z); + var material = new THREE.MeshBasicMaterial({ + color : color.getHex() + }); + // setup the rotation information + var rotAxis = new THREE.Vector3(); + rotAxis.crossVectors(axis, new THREE.Vector3(0, -1, 0)); + var rot = new THREE.Quaternion(); + rot.setFromAxisAngle(rotAxis, 0.5 * Math.PI); - // create the cylinders for the objects - this.lineGeom = new THREE$1.CylinderGeometry(shaftRadius, shaftRadius, 1.0 - headLength); - this.headGeom = new THREE$1.CylinderGeometry(0, headRadius, headLength); + // create the arrow + var arrow = new THREE.Mesh(that.headGeom, material); + arrow.position = axis.clone(); + arrow.position.multiplyScalar(0.95); + arrow.quaternion = rot; + arrow.updateMatrix(); + that.add(arrow); - /** - * Adds an axis marker to this axes object. - * - * @param axis - the 3D vector representing the axis to add - */ - function addAxis(axis) { - // set the color of the axis - var color = new THREE$1.Color(); - color.setRGB(axis.x, axis.y, axis.z); - var material = new THREE$1.MeshBasicMaterial({ - color : color.getHex() - }); + // create the line + var line = new THREE.Mesh(that.lineGeom, material); + line.position = axis.clone(); + line.position.multiplyScalar(0.45); + line.quaternion = rot; + line.updateMatrix(); + that.add(line); + } - // setup the rotation information - var rotAxis = new THREE$1.Vector3(); - rotAxis.crossVectors(axis, new THREE$1.Vector3(0, -1, 0)); - var rot = new THREE$1.Quaternion(); - rot.setFromAxisAngle(rotAxis, 0.5 * Math.PI); - - // create the arrow - var arrow = new THREE$1.Mesh(that.headGeom, material); - arrow.position.copy(axis); - arrow.position.multiplyScalar(0.95); - arrow.quaternion.copy(rot); - arrow.updateMatrix(); - that.add(arrow); - - // create the line - var line = new THREE$1.Mesh(that.lineGeom, material); - line.position.copy(axis); - line.position.multiplyScalar(0.45); - line.quaternion.copy(rot); - line.updateMatrix(); - that.add(line); - } - - // add the three markers to the axes - addAxis(new THREE$1.Vector3(1, 0, 0)); - addAxis(new THREE$1.Vector3(0, 1, 0)); - addAxis(new THREE$1.Vector3(0, 0, 1)); - }; -} + // add the three markers to the axes + addAxis(new THREE.Vector3(1, 0, 0)); + addAxis(new THREE.Vector3(0, 1, 0)); + addAxis(new THREE.Vector3(0, 0, 1)); +}; +ROS3D.Axes.prototype.__proto__ = THREE.Object3D.prototype; /** * @author Russell Toris - rctoris@wpi.edu */ -class Grid extends THREE$1.Object3D { - - /** - * Create a grid object. - * - * @constructor - * @param options - object with following keys: - * - * * num_cells (optional) - The number of cells of the grid - * * color (optional) - the line color of the grid, like '#cccccc' - * * lineWidth (optional) - the width of the lines in the grid - * * cellSize (optional) - The length, in meters, of the side of each cell - */ - constructor(options) { - options = options || {}; - var num_cells = options.num_cells || 10; - var color = options.color || '#cccccc'; - var lineWidth = options.lineWidth || 1; - var cellSize = options.cellSize || 1; - - super(); - - var material = new THREE$1.LineBasicMaterial({ - color: color, - linewidth: lineWidth - }); - - for (var i = 0; i <= num_cells; ++i) { - var edge = cellSize * num_cells / 2; - var position = edge - (i * cellSize); - var geometryH = new THREE$1.Geometry(); - geometryH.vertices.push( - new THREE$1.Vector3( -edge, position, 0 ), - new THREE$1.Vector3( edge, position, 0 ) - ); - var geometryV = new THREE$1.Geometry(); - geometryV.vertices.push( - new THREE$1.Vector3( position, -edge, 0 ), - new THREE$1.Vector3( position, edge, 0 ) - ); - this.add(new THREE$1.Line(geometryH, material)); - this.add(new THREE$1.Line(geometryV, material)); - } - }; -} - /** - * @author Russell Toris - rctoris@wpi.edu + * Create a grid object. + * + * @constructor + * @param options - object with following keys: + * + * * size (optional) - The number of cells of the grid + * * color (optional) - the line color of the grid, like '#cccccc' + * * lineWidth (optional) - the width of the lines in the grid + * * cellSize (optional) - The length, in meters, of the side of each cell */ +ROS3D.Grid = function(options) { + options = options || {}; + var size = options.size || 10; + var color = options.color || '#cccccc'; + var lineWidth = options.lineWidth || 1; + var cellSize = options.cellSize || 1; -class OccupancyGrid extends THREE$1.Mesh { + THREE.Object3D.call(this); - /** - * An OccupancyGrid can convert a ROS occupancy grid message into a THREE object. - * - * @constructor - * @param options - object with following keys: - * - * * message - the occupancy grid message - * * color (optional) - color of the visualized grid - * * opacity (optional) - opacity of the visualized grid (0.0 == fully transparent, 1.0 == opaque) - */ - constructor(options) { - options = options || {}; - var message = options.message; - var color = options.color || {r:255,g:255,b:255}; - var opacity = options.opacity || 1.0; + var material = new THREE.LineBasicMaterial({ + color: color, + linewidth: lineWidth + }); - // create the geometry - var width = message.info.width; - var height = message.info.height; - var geom = new THREE$1.PlaneGeometry(width, height); - - // internal drawing canvas - var canvas = document.createElement('canvas'); - canvas.width = width; - canvas.height = height; - var context = canvas.getContext('2d'); - // create the color material - var imageData = context.createImageData(width, height); - for ( var row = 0; row < height; row++) { - for ( var col = 0; col < width; col++) { - // determine the index into the map data - var mapI = col + ((height - row - 1) * width); - // determine the value - var data = message.data[mapI]; - var val; - if (data === 100) { - val = 0; - } else if (data === 0) { - val = 255; - } else { - val = 127; - } - - // determine the index into the image data array - var i = (col + (row * width)) * 4; - // r - imageData.data[i] = (val * color.r) / 255; - // g - imageData.data[++i] = (val * color.g) / 255; - // b - imageData.data[++i] = (val * color.b) / 255; - // a - imageData.data[++i] = 255; - } - } - context.putImageData(imageData, 0, 0); - - var texture = new THREE$1.Texture(canvas); - texture.needsUpdate = true; - - var material = new THREE$1.MeshBasicMaterial({ - map : texture, - transparent : opacity < 1.0, - opacity : opacity - }); - material.side = THREE$1.DoubleSide; - - // create the mesh - super(geom, material); - // move the map so the corner is at X, Y and correct orientation (informations from message.info) - this.quaternion = new THREE$1.Quaternion( - message.info.origin.orientation.x, - message.info.origin.orientation.y, - message.info.origin.orientation.z, - message.info.origin.orientation.w + for (var i = 0; i <= size; ++i) { + var edge = cellSize * size / 2; + var position = edge - (i * cellSize); + var geometryH = new THREE.Geometry(); + geometryH.vertices.push( + new THREE.Vector3( -edge, position, 0 ), + new THREE.Vector3( edge, position, 0 ) ); - this.position.x = (width * message.info.resolution) / 2 + message.info.origin.position.x; - this.position.y = (height * message.info.resolution) / 2 + message.info.origin.position.y; - this.position.z = message.info.origin.position.z; - this.scale.x = message.info.resolution; - this.scale.y = message.info.resolution; - }; -} - -/** - * @author Russell Toris - rctoris@wpi.edu - */ - -class OccupancyGridClient extends eventemitter2 { - - /** - * An occupancy grid client that listens to a given map topic. - * - * Emits the following events: - * - * * 'change' - there was an update or change in the marker - * - * @constructor - * @param options - object with following keys: - * - * * ros - the ROSLIB.Ros connection handle - * * topic (optional) - the map topic to listen to - * * continuous (optional) - if the map should be continuously loaded (e.g., for SLAM) - * * tfClient (optional) - the TF client handle to use for a scene node - * * rootObject (optional) - the root object to add this marker to - * * offsetPose (optional) - offset pose of the grid visualization, e.g. for z-offset (ROSLIB.Pose type) - * * color (optional) - color of the visualized grid - * * opacity (optional) - opacity of the visualized grid (0.0 == fully transparent, 1.0 == opaque) - */ - constructor(options) { - super(); - options = options || {}; - this.ros = options.ros; - this.topicName = options.topic || '/map'; - this.continuous = options.continuous; - this.tfClient = options.tfClient; - this.rootObject = options.rootObject || new THREE$1.Object3D(); - this.offsetPose = options.offsetPose || new ROSLIB.Pose(); - this.color = options.color || {r:255,g:255,b:255}; - this.opacity = options.opacity || 1.0; - - // current grid that is displayed - this.currentGrid = null; - - // subscribe to the topic - this.rosTopic = undefined; - this.subscribe(); - }; - - unsubscribe(){ - if(this.rosTopic){ - this.rosTopic.unsubscribe(); - } - }; - - subscribe(){ - this.unsubscribe(); - - // subscribe to the topic - this.rosTopic = new ROSLIB.Topic({ - ros : this.ros, - name : this.topicName, - messageType : 'nav_msgs/OccupancyGrid', - compression : 'png' - }); - this.rosTopic.subscribe(this.processMessage.bind(this)); - }; - - processMessage(message){ - // check for an old map - if (this.currentGrid) { - // check if it there is a tf client - if (this.currentGrid.tfClient) { - // grid is of type ROS3D.SceneNode - this.currentGrid.unsubscribeTf(); - } - this.rootObject.remove(this.currentGrid); - } - - var newGrid = new OccupancyGrid({ - message : message, - color : this.color, - opacity : this.opacity - }); - - // check if we care about the scene - if (this.tfClient) { - this.currentGrid = newGrid; - this.sceneNode = new SceneNode({ - frameID : message.header.frame_id, - tfClient : this.tfClient, - object : newGrid, - pose : this.offsetPose - }); - } else { - this.sceneNode = this.currentGrid = newGrid; - } - - this.rootObject.add(this.sceneNode); - - this.emit('change'); - - // check if we should unsubscribe - if (!this.continuous) { - this.rosTopic.unsubscribe(); - } - }; -} - -/** - * @author David V. Lu!! - davidvlu@gmail.com - */ - -class Odometry extends THREE$1.Object3D { - - /** - * An Odometry client - * - * @constructor - * @param options - object with following keys: - * - * * ros - the ROSLIB.Ros connection handle - * * topic - the marker topic to listen to - * * tfClient - the TF client handle to use - * * rootObject (optional) - the root object to add this marker to - * * keep (optional) - number of markers to keep around (default: 1) - * * color (optional) - color for line (default: 0xcc00ff) - * * length (optional) - the length of the arrow (default: 1.0) - * * headLength (optional) - the head length of the arrow (default: 0.2) - * * shaftDiameter (optional) - the shaft diameter of the arrow (default: 0.05) - * * headDiameter (optional) - the head diameter of the arrow (default: 0.1) - */ - constructor(options) { - super(); - this.options = options || {}; - this.ros = options.ros; - this.topicName = options.topic || '/particlecloud'; - this.tfClient = options.tfClient; - this.color = options.color || 0xcc00ff; - this.length = options.length || 1.0; - this.rootObject = options.rootObject || new THREE$1.Object3D(); - this.keep = options.keep || 1; - - this.sns = []; - - this.rosTopic = undefined; - this.subscribe(); - }; - - - unsubscribe(){ - if(this.rosTopic){ - this.rosTopic.unsubscribe(); - } - }; - - subscribe(){ - this.unsubscribe(); - - // subscribe to the topic - this.rosTopic = new ROSLIB.Topic({ - ros : this.ros, - name : this.topicName, - messageType : 'nav_msgs/Odometry' - }); - this.rosTopic.subscribe(this.processMessage.bind(this)); - }; - - processMessage(message){ - if(this.sns.length >= this.keep) { - this.sns[0].unsubscribeTf(); - this.rootObject.remove(this.sns[0]); - this.sns.shift(); - } - - this.options.origin = new THREE$1.Vector3( message.pose.pose.position.x, message.pose.pose.position.y, - message.pose.pose.position.z); - - var rot = new THREE$1.Quaternion(message.pose.pose.orientation.x, message.pose.pose.orientation.y, - message.pose.pose.orientation.z, message.pose.pose.orientation.w); - this.options.direction = new THREE$1.Vector3(1,0,0); - this.options.direction.applyQuaternion(rot); - this.options.material = new THREE$1.MeshBasicMaterial({color: this.color}); - var arrow = new Arrow(this.options); - - this.sns.push(new SceneNode({ - frameID : message.header.frame_id, - tfClient : this.tfClient, - object : arrow - })); - - this.rootObject.add(this.sns[ this.sns.length - 1]); - }; -} - -/** - * @author David V. Lu!! - davidvlu@gmail.com - */ - -class Path$1 extends THREE$1.Object3D { - - /** - * A Path client that listens to a given topic and displays a line connecting the poses. - * - * @constructor - * @param options - object with following keys: - * - * * ros - the ROSLIB.Ros connection handle - * * topic - the marker topic to listen to - * * tfClient - the TF client handle to use - * * rootObject (optional) - the root object to add this marker to - * * color (optional) - color for line (default: 0xcc00ff) - */ - constructor(options) { - super(); - options = options || {}; - this.ros = options.ros; - this.topicName = options.topic || '/path'; - this.tfClient = options.tfClient; - this.color = options.color || 0xcc00ff; - this.rootObject = options.rootObject || new THREE$1.Object3D(); - - this.sn = null; - this.line = null; - - this.rosTopic = undefined; - this.subscribe(); - }; - - - unsubscribe(){ - if(this.rosTopic){ - this.rosTopic.unsubscribe(); - } - }; - - subscribe(){ - this.unsubscribe(); - - // subscribe to the topic - this.rosTopic = new ROSLIB.Topic({ - ros : this.ros, - name : this.topicName, - messageType : 'nav_msgs/Path' - }); - this.rosTopic.subscribe(this.processMessage.bind(this)); - }; - - processMessage(message){ - if(this.sn!==null){ - this.sn.unsubscribeTf(); - this.rootObject.remove(this.sn); - } - - var lineGeometry = new THREE$1.Geometry(); - for(var i=0; i= message.range_min && range <= message.range_max){ - var angle = message.angle_min + i * message.angle_increment; - this.points.positions.array[j++] = range * Math.cos(angle); - this.points.positions.array[j++] = range * Math.sin(angle); - this.points.positions.array[j++] = 0.0; - } - } - this.points.update(j/3); - }; -} - -/** - * @author David V. Lu!! - davidvlu@gmail.com - * @author Mathieu Bredif - mathieu.bredif@ign.fr - */ - -/** - * Decodes the base64-encoded array 'inbytes' into the array 'outbytes' - * until 'inbytes' is exhausted or 'outbytes' is filled. - * if 'record_size' is specified, records of length 'record_size' bytes - * are copied every other 'pointRatio' records. - * returns the number of decoded records - */ -function decode64(inbytes, outbytes, record_size, pointRatio) { - var x,b=0,l=0,j=0,L=inbytes.length,A=outbytes.length; - record_size = record_size || A; // default copies everything (no skipping) - pointRatio = pointRatio || 1; // default copies everything (no skipping) - var bitskip = (pointRatio-1) * record_size * 8; - for(x=0;x=8){ - l-=8; - outbytes[j++]=(b>>>l)&0xff; - if((j % record_size) === 0) { // skip records - // no optimization: for(var i=0;i=8) {l-=8;i+=8;}} - // first optimization: for(;l0){b=decode64.e[inbytes.charAt(x)];} - } - } - } - return Math.floor(j/record_size); -} -// initialize decoder with static lookup table 'e' -decode64.S='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; -decode64.e={}; -for(var i=0;i<64;i++){decode64.e[decode64.S.charAt(i)]=i;} - - -class PointCloud2 extends THREE$1.Object3D { - - /** - * A PointCloud2 client that listens to a given topic and displays the points. - * - * @constructor - * @param options - object with following keys: - * - * * ros - the ROSLIB.Ros connection handle - * * topic - the marker topic to listen to (default: '/points') - * * tfClient - the TF client handle to use - * * rootObject (optional) - the root object to add this marker to use for the points. - * * max_pts (optional) - number of points to draw (default: 10000) - * * pointRatio (optional) - point subsampling ratio (default: 1, no subsampling) - * * messageRatio (optional) - message subsampling ratio (default: 1, no subsampling) - * * material (optional) - a material object or an option to construct a PointsMaterial. - * * colorsrc (optional) - the field to be used for coloring (default: 'rgb') - * * colormap (optional) - function that turns the colorsrc field value to a color - */ - constructor(options) { - super(); - options = options || {}; - this.ros = options.ros; - this.topicName = options.topic || '/points'; - this.points = new Points$1(options); - this.rosTopic = undefined; - this.subscribe(); - }; - - - unsubscribe(){ - if(this.rosTopic){ - this.rosTopic.unsubscribe(); - } - }; - - subscribe(){ - this.unsubscribe(); - - // subscribe to the topic - this.rosTopic = new ROSLIB.Topic({ - ros : this.ros, - name : this.topicName, - messageType : 'sensor_msgs/PointCloud2' - }); - this.rosTopic.subscribe(this.processMessage.bind(this)); - }; - - processMessage(msg){ - if(!this.points.setup(msg.header.frame_id, msg.point_step, msg.fields)) { - return; - } - - var n, pointRatio = this.points.pointRatio; - - if (msg.data.buffer) { - this.points.buffer = msg.data.buffer; - n = msg.height*msg.width / pointRatio; - } else { - n = decode64(msg.data, this.points.buffer, msg.point_step, pointRatio); - pointRatio = 1; - } - - var dv = new DataView(this.points.buffer.buffer); - var littleEndian = !msg.is_bigendian; - var x = this.points.fields.x.offset; - var y = this.points.fields.y.offset; - var z = this.points.fields.z.offset; - var base, color; - for(var i = 0; i < n; i++){ - base = i * pointRatio * msg.point_step; - this.points.positions.array[3*i ] = dv.getFloat32(base+x, littleEndian); - this.points.positions.array[3*i + 1] = dv.getFloat32(base+y, littleEndian); - this.points.positions.array[3*i + 2] = dv.getFloat32(base+z, littleEndian); - - if(this.points.colors){ - color = this.points.colormap(this.points.getColor(dv,base,littleEndian)); - this.points.colors.array[3*i ] = color.r; - this.points.colors.array[3*i + 1] = color.g; - this.points.colors.array[3*i + 2] = color.b; - } - } - this.points.update(n); - }; -} + var geometryV = new THREE.Geometry(); + geometryV.vertices.push( + new THREE.Vector3( position, -edge, 0 ), + new THREE.Vector3( position, edge, 0 ) + ); + this.add(new THREE.Line(geometryH, material)); + this.add(new THREE.Line(geometryV, material)); + } +}; + +ROS3D.Grid.prototype.__proto__ = THREE.Object3D.prototype; /** * @author Jihoon Lee - jihoonlee.in@gmail.com * @author Russell Toris - rctoris@wpi.edu */ -class Urdf extends THREE$1.Object3D { +/** + * A MeshResource is an THREE object that will load from a external mesh file. Currently loads + * Collada files. + * + * @constructor + * @param options - object with following keys: + * + * * path (optional) - the base path to the associated models that will be loaded + * * resource - the resource file name to load + * * material (optional) - the material to use for the object + * * warnings (optional) - if warnings should be printed + * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER + * ROS3D.COLLADA_LOADER_2) -- defaults to ROS3D.COLLADA_LOADER_2 + */ +ROS3D.MeshResource = function(options) { + var that = this; + options = options || {}; + var path = options.path || '/'; + var resource = options.resource; + var material = options.material || null; + this.warnings = options.warnings; + var loaderType = options.loader || ROS3D.COLLADA_LOADER_2; - /** - * A URDF can be used to load a ROSLIB.UrdfModel and its associated models into a 3D object. - * - * @constructor - * @param options - object with following keys: - * - * * urdfModel - the ROSLIB.UrdfModel to load - * * tfClient - the TF client handle to use - * * path (optional) - the base path to the associated Collada models that will be loaded - * * tfPrefix (optional) - the TF prefix to used for multi-robots - * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER) - */ - constructor(options) { - options = options || {}; - var urdfModel = options.urdfModel; - var path = options.path || '/'; - var tfClient = options.tfClient; - var tfPrefix = options.tfPrefix || ''; - var loader = options.loader; + THREE.Object3D.call(this); - super(); + // check for a trailing '/' + if (path.substr(path.length - 1) !== '/') { + this.path += '/'; + } + + var uri = path + resource; + var fileType = uri.substr(-4).toLowerCase(); + + // check the type + var loader; + if (fileType === '.dae') { + if (loaderType === ROS3D.COLLADA_LOADER) { + loader = new THREE.ColladaLoader(); + } else { + loader = new ColladaLoader2(); + } + loader.log = function(message) { + if (that.warnings) { + console.warn(message); + } + }; + loader.load(uri, function colladaReady(collada) { + // check for a scale factor in ColladaLoader2 + if(loaderType === ROS3D.COLLADA_LOADER_2 && collada.dae.asset.unit) { + var scale = collada.dae.asset.unit; + collada.scene.scale = new THREE.Vector3(scale, scale, scale); + } + + // add a texture to anything that is missing one + if(material !== null) { + var setMaterial = function(node, material) { + // do not overwrite the material + if (typeof node.material === 'undefined') { + node.material = material; + } + //node.material = material; + if (node.children) { + for (var i = 0; i < node.children.length; i++) { + setMaterial(node.children[i], material); + } + } + }; + + setMaterial(collada.scene, material); + } + + that.add(collada.scene); + }); + } else if (fileType === '.stl') { + loader = new THREE.STLLoader(); + { + loader.load(uri, function ( geometry ) { + geometry.computeFaceNormals(); + var mesh; + if(material !== null) { + mesh = new THREE.Mesh( geometry, material ); + } else { + mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: 0x999999 } ) ); + } + that.add(mesh); + } ); + } + } +}; +ROS3D.MeshResource.prototype.__proto__ = THREE.Object3D.prototype; + +/** + * @author David Gossow - dgossow@willowgarage.com + */ + +/** + * A TriangleList is a THREE object that can be used to display a list of triangles as a geometry. + * + * @constructor + * @param options - object with following keys: + * + * * material (optional) - the material to use for the object + * * vertices - the array of vertices to use + * * colors - the associated array of colors to use + */ +ROS3D.TriangleList = function(options) { + options = options || {}; + var material = options.material || new THREE.MeshBasicMaterial(); + var vertices = options.vertices; + var colors = options.colors; + + THREE.Object3D.call(this); + + // set the material to be double sided + material.side = THREE.DoubleSide; + + // construct the geometry + var geometry = new THREE.Geometry(); + for (i = 0; i < vertices.length; i++) { + geometry.vertices.push(new THREE.Vector3(vertices[i].x, vertices[i].y, vertices[i].z)); + } + + // set the colors + var i, j; + if (colors.length === vertices.length) { + // use per-vertex color + for (i = 0; i < vertices.length; i += 3) { + var faceVert = new THREE.Face3(i, i + 1, i + 2); + for (j = i * 3; j < i * 3 + 3; i++) { + var color = new THREE.Color(); + color.setRGB(colors[i].r, colors[i].g, colors[i].b); + faceVert.vertexColors.push(color); + } + geometry.faces.push(face); + } + material.vertexColors = THREE.VertexColors; + } else if (colors.length === vertices.length / 3) { + // use per-triangle color + for (i = 0; i < vertices.length; i += 3) { + var faceTri = new THREE.Face3(i, i + 1, i + 2); + faceTri.color.setRGB(colors[i / 3].r, colors[i / 3].g, colors[i / 3].b); + geometry.faces.push(faceTri); + } + material.vertexColors = THREE.FaceColors; + } else { + // use marker color + for (i = 0; i < vertices.length; i += 3) { + var face = new THREE.Face3(i, i + 1, i + 2); + geometry.faces.push(face); + } + } + + geometry.computeBoundingBox(); + geometry.computeBoundingSphere(); + geometry.computeCentroids(); + geometry.computeFaceNormals(); + + this.add(new THREE.Mesh(geometry, material)); +}; +ROS3D.TriangleList.prototype.__proto__ = THREE.Object3D.prototype; + +/** + * Set the color of this object to the given hex value. + * + * @param hex - the hex value of the color to set + */ +ROS3D.TriangleList.prototype.setColor = function(hex) { + this.mesh.material.color.setHex(hex); +}; + +/** + * @author Jihoon Lee - jihoonlee.in@gmail.com + * @author Russell Toris - rctoris@wpi.edu + */ + +/** + * A URDF can be used to load a ROSLIB.UrdfModel and its associated models into a 3D object. + * + * @constructor + * @param options - object with following keys: + * + * * urdfModel - the ROSLIB.UrdfModel to load + * * tfClient - the TF client handle to use + * * path (optional) - the base path to the associated Collada models that will be loaded + * * tfPrefix (optional) - the TF prefix to used for multi-robots + * * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER + * ROS3D.COLLADA_LOADER_2) -- defaults to ROS3D.COLLADA_LOADER_2 + */ +ROS3D.Urdf = function(options) { + options = options || {}; + var urdfModel = options.urdfModel; + var path = options.path || '/'; + var tfClient = options.tfClient; + var tfPrefix = options.tfPrefix || ''; + var loader = options.loader || ROS3D.COLLADA_LOADER_2; + + THREE.Object3D.call(this); + + // load all models + var links = urdfModel.links; + for ( var l in links) { + var link = links[l]; + for( var i=0; i= 0; o--) { + if (objlist[o].object === objects[c]) { + renderList.push(objlist[o]); + break; } - }; - - for (var uuid in this.hoverObjs) { - var selectedObject = this.hoverObjs[uuid]; - // Make each selected object and all of its children visible - selectedObject.visible = true; - selectedObject.traverse(makeVisible); - } - }; - - /** - * Restore the old visibility state that was saved by - * makeEverythinginvisible. - * - * @param scene - the object to traverse - */ - restoreVisibility (scene) { - scene.traverse(function(currentObject) { - if (currentObject.hasOwnProperty('previousVisibility')) { - currentObject.visible = currentObject.previousVisibility; } - }.bind(this)); - }; -} + // recurse into children + this.getWebglObjects(scene, objects[c].children, renderList); + } + } +}; + +/** + * Render highlighted objects in the scene. + * + * @param renderer - the renderer to use + * @param scene - the scene to use + * @param camera - the camera to use + */ +ROS3D.Highlighter.prototype.renderHighlight = function(renderer, scene, camera) { + // get webgl objects + var renderList = []; + this.getWebglObjects(scene, this.hoverObjs, renderList); + + // define highlight material + scene.overrideMaterial = new THREE.MeshBasicMaterial({ + fog : false, + opacity : 0.5, + depthTest : true, + depthWrite : false, + polygonOffset : true, + polygonOffsetUnits : -1, + side : THREE.DoubleSide + }); + + // swap render lists, render, undo + var oldWebglObjects = scene.__webglObjects; + scene.__webglObjects = renderList; + + renderer.render(scene, camera); + + scene.__webglObjects = oldWebglObjects; + scene.overrideMaterial = null; +}; /** * @author David Gossow - dgossow@willowgarage.com */ -class MouseHandler extends THREE$1.EventDispatcher { +/** + * A handler for mouse events within a 3D viewer. + * + * @constructor + * @param options - object with following keys: + * + * * renderer - the main renderer + * * camera - the main camera in the scene + * * rootObject - the root object to check for mouse events + * * fallbackTarget - the fallback target, e.g., the camera controls + */ +ROS3D.MouseHandler = function(options) { + THREE.EventDispatcher.call(this); + this.renderer = options.renderer; + this.camera = options.camera; + this.rootObject = options.rootObject; + this.fallbackTarget = options.fallbackTarget; + this.lastTarget = this.fallbackTarget; + this.dragging = false; + this.projector = new THREE.Projector(); - /** - * A handler for mouse events within a 3D viewer. - * - * @constructor - * @param options - object with following keys: - * - * * renderer - the main renderer - * * camera - the main camera in the scene - * * rootObject - the root object to check for mouse events - * * fallbackTarget - the fallback target, e.g., the camera controls - */ - constructor(options) { - super(); - this.renderer = options.renderer; - this.camera = options.camera; - this.rootObject = options.rootObject; - this.fallbackTarget = options.fallbackTarget; - this.lastTarget = this.fallbackTarget; - this.dragging = false; + // listen to DOM events + var eventNames = [ 'contextmenu', 'click', 'dblclick', 'mouseout', 'mousedown', 'mouseup', + 'mousemove', 'mousewheel', 'DOMMouseScroll', 'touchstart', 'touchend', 'touchcancel', + 'touchleave', 'touchmove' ]; + this.listeners = {}; - // listen to DOM events - var eventNames = [ 'contextmenu', 'click', 'dblclick', 'mouseout', 'mousedown', 'mouseup', - 'mousemove', 'mousewheel', 'DOMMouseScroll', 'touchstart', 'touchend', 'touchcancel', - 'touchleave', 'touchmove' ]; - this.listeners = {}; + // add event listeners for the associated mouse events + eventNames.forEach(function(eventName) { + this.listeners[eventName] = this.processDomEvent.bind(this); + this.renderer.domElement.addEventListener(eventName, this.listeners[eventName], false); + }, this); +}; - // add event listeners for the associated mouse events - eventNames.forEach(function(eventName) { - this.listeners[eventName] = this.processDomEvent.bind(this); - this.renderer.domElement.addEventListener(eventName, this.listeners[eventName], false); - }, this); +/** + * Process the particular DOM even that has occurred based on the mouse's position in the scene. + * + * @param domEvent - the DOM event to process + */ +ROS3D.MouseHandler.prototype.processDomEvent = function(domEvent) { + // don't deal with the default handler + domEvent.preventDefault(); + + // compute normalized device coords and 3D mouse ray + var target = domEvent.target; + var rect = target.getBoundingClientRect(); + var pos_x, pos_y; + + if(domEvent.type.indexOf('touch') !== -1) { + pos_x = domEvent.changedTouches[0].clientX; + pos_y = domEvent.changedTouches[0].clientY; + } + else { + pos_x = domEvent.clientX; + pos_y = domEvent.clientY; + } + var left = pos_x - rect.left - target.clientLeft + target.scrollLeft; + var top = pos_y - rect.top - target.clientTop + target.scrollTop; + var deviceX = left / target.clientWidth * 2 - 1; + var deviceY = -top / target.clientHeight * 2 + 1; + var vector = new THREE.Vector3(deviceX, deviceY, 0.5); + this.projector.unprojectVector(vector, this.camera); + // use the THREE raycaster + var mouseRaycaster = new THREE.Raycaster(this.camera.position.clone(), vector.sub( + this.camera.position).normalize()); + mouseRaycaster.linePrecision = 0.001; + var mouseRay = mouseRaycaster.ray; + + // make our 3d mouse event + var event3D = { + mousePos : new THREE.Vector2(deviceX, deviceY), + mouseRay : mouseRay, + domEvent : domEvent, + camera : this.camera, + intersection : this.lastIntersection }; - /** - * Process the particular DOM even that has occurred based on the mouse's position in the scene. - * - * @param domEvent - the DOM event to process - */ - processDomEvent(domEvent) { - // don't deal with the default handler - domEvent.preventDefault(); - - // compute normalized device coords and 3D mouse ray - var target = domEvent.target; - var rect = target.getBoundingClientRect(); - var pos_x, pos_y; - - if(domEvent.type.indexOf('touch') !== -1) { - pos_x = 0; - pos_y = 0; - for(var i=0; i 0) { - target = intersections[0].object; - event3D.intersection = this.lastIntersection = intersections[0]; + // while the user is holding the mouse down, stay on the same target + if (this.dragging) { + this.notify(this.lastTarget, domEvent.type, event3D); + // for check for right or left mouse button + if ((domEvent.type === 'mouseup' && domEvent.button === 2) || domEvent.type === 'click' || domEvent.type === 'touchend') { + this.dragging = false; + } + return; + } + + // in the normal case, we need to check what is under the mouse + target = this.lastTarget; + var intersections = []; + intersections = mouseRaycaster.intersectObject(this.rootObject, true); + if (intersections.length > 0) { + target = intersections[0].object; + event3D.intersection = this.lastIntersection = intersections[0]; + } else { + target = this.fallbackTarget; + } + + // if the mouse moves from one object to another (or from/to the 'null' object), notify both + if (target !== this.lastTarget && domEvent.type.match(/mouse/)) { + var eventAccepted = this.notify(target, 'mouseover', event3D); + if (eventAccepted) { + this.notify(this.lastTarget, 'mouseout', event3D); } else { + // if target was null or no target has caught our event, fall back target = this.fallbackTarget; - } - - // if the mouse moves from one object to another (or from/to the 'null' object), notify both - if (target !== this.lastTarget && domEvent.type.match(/mouse/)) { - - // Event Status. TODO: Make it as enum - // 0: Accepted - // 1: Failed - // 2: Continued - var eventStatus = this.notify(target, 'mouseover', event3D); - if (eventStatus === 0) { + if (target !== this.lastTarget) { + this.notify(target, 'mouseover', event3D); this.notify(this.lastTarget, 'mouseout', event3D); - } else if(eventStatus === 1) { - // if target was null or no target has caught our event, fall back - target = this.fallbackTarget; - if (target !== this.lastTarget) { - this.notify(target, 'mouseover', event3D); - this.notify(this.lastTarget, 'mouseout', event3D); - } } } + } - // if the finger moves from one object to another (or from/to the 'null' object), notify both - if (target !== this.lastTarget && domEvent.type.match(/touch/)) { - var toucheventAccepted = this.notify(target, domEvent.type, event3D); - if (toucheventAccepted) { - this.notify(this.lastTarget, 'touchleave', event3D); + // if the finger moves from one object to another (or from/to the 'null' object), notify both + if (target !== this.lastTarget && domEvent.type.match(/touch/)) { + var toucheventAccepted = this.notify(target, domEvent.type, event3D); + if (toucheventAccepted) { + this.notify(this.lastTarget, 'touchleave', event3D); + this.notify(this.lastTarget, 'touchend', event3D); + } else { + // if target was null or no target has caught our event, fall back + target = this.fallbackTarget; + if (target !== this.lastTarget) { + this.notify(this.lastTarget, 'touchmove', event3D); this.notify(this.lastTarget, 'touchend', event3D); - } else { - // if target was null or no target has caught our event, fall back - target = this.fallbackTarget; - if (target !== this.lastTarget) { - this.notify(this.lastTarget, 'touchmove', event3D); - this.notify(this.lastTarget, 'touchend', event3D); - } } } + } - // pass through event - this.notify(target, domEvent.type, event3D); - if (domEvent.type === 'mousedown' || domEvent.type === 'touchstart' || domEvent.type === 'touchmove') { - this.dragging = true; - } - this.lastTarget = target; + // pass through event + this.notify(target, domEvent.type, event3D); + if (domEvent.type === 'mousedown' || domEvent.type === 'touchstart' || domEvent.type === 'touchmove') { + this.dragging = true; + } + this.lastTarget = target; +}; + +/** + * Notify the listener of the type of event that occurred. + * + * @param target - the target of the event + * @param type - the type of event that occurred + * @param event3D - the 3D mouse even information + * @returns if an event was canceled + */ +ROS3D.MouseHandler.prototype.notify = function(target, type, event3D) { + // ensure the type is set + event3D.type = type; + + // make the event cancelable + event3D.cancelBubble = false; + event3D.stopPropagation = function() { + event3D.cancelBubble = true; }; - - /** - * Notify the listener of the type of event that occurred. - * - * @param target - the target of the event - * @param type - the type of event that occurred - * @param event3D - the 3D mouse even information - * @returns if an event was canceled - */ - notify(target, type, event3D) { - // ensure the type is set - // - event3D.type = type; - - // make the event cancelable - event3D.cancelBubble = false; - event3D.continueBubble = false; - event3D.stopPropagation = function() { - event3D.cancelBubble = true; - }; - - // it hit the selectable object but don't highlight - event3D.continuePropagation = function () { - event3D.continueBubble = true; - }; - - // walk up graph until event is canceled or root node has been reached - event3D.currentTarget = target; - - while (event3D.currentTarget) { - // try to fire event on object - if (event3D.currentTarget.dispatchEvent - && event3D.currentTarget.dispatchEvent instanceof Function) { - event3D.currentTarget.dispatchEvent(event3D); - if (event3D.cancelBubble) { - this.dispatchEvent(event3D); - return 0; // Event Accepted - } - else if(event3D.continueBubble) { - return 2; // Event Continued - } + // walk up graph until event is canceled or root node has been reached + event3D.currentTarget = target; + while (event3D.currentTarget) { + // try to fire event on object + if (event3D.currentTarget.dispatchEvent + && event3D.currentTarget.dispatchEvent instanceof Function) { + event3D.currentTarget.dispatchEvent(event3D); + if (event3D.cancelBubble) { + this.dispatchEvent(event3D); + return true; } - // walk up - event3D.currentTarget = event3D.currentTarget.parent; } + // walk up + event3D.currentTarget = event3D.currentTarget.parent; + } + return false; +}; - return 1; // Event Failed - }; -} +THREE.EventDispatcher.prototype.apply( ROS3D.MouseHandler.prototype ); /** * @author David Gossow - dgossow@willowgarage.com @@ -54065,745 +3391,497 @@ class MouseHandler extends THREE$1.EventDispatcher { * @author AlteredQualia - http://alteredqualia.com */ -class OrbitControls extends THREE$1.EventDispatcher { +/** + * Behaves like THREE.OrbitControls, but uses right-handed coordinates and z as up vector. + * + * @constructor + * @param scene - the global scene to use + * @param camera - the camera to use + * @param userZoomSpeed (optional) - the speed for zooming + * @param userRotateSpeed (optional) - the speed for rotating + * @param autoRotate (optional) - if the orbit should auto rotate + * @param autoRotate (optional) - the speed for auto rotating + */ +ROS3D.OrbitControls = function(options) { + THREE.EventDispatcher.call(this); + var that = this; + options = options || {}; + var scene = options.scene; + this.camera = options.camera; + this.center = new THREE.Vector3(); + this.userZoom = true; + this.userZoomSpeed = options.userZoomSpeed || 1.0; + this.userRotate = true; + this.userRotateSpeed = options.userRotateSpeed || 1.0; + this.autoRotate = options.autoRotate; + this.autoRotateSpeed = options.autoRotateSpeed || 2.0; + + // In ROS, z is pointing upwards + this.camera.up = new THREE.Vector3(0, 0, 1); + + // internals + var pixelsPerRound = 1800; + var touchMoveThreshold = 10; + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + var zoomStart = new THREE.Vector2(); + var zoomEnd = new THREE.Vector2(); + var zoomDelta = new THREE.Vector2(); + var moveStartCenter = new THREE.Vector3(); + var moveStartNormal = new THREE.Vector3(); + var moveStartPosition = new THREE.Vector3(); + var moveStartIntersection = new THREE.Vector3(); + var touchStartPosition = new Array(2); + var touchMoveVector = new Array(2); + this.phiDelta = 0; + this.thetaDelta = 0; + this.scale = 1; + this.lastPosition = new THREE.Vector3(); + // internal states + var STATE = { + NONE : -1, + ROTATE : 0, + ZOOM : 1, + MOVE : 2 + }; + var state = STATE.NONE; + + // add the axes for the main coordinate frame + this.axes = new ROS3D.Axes({ + shaftRadius : 0.025, + headRadius : 0.07, + headLength : 0.2 + }); + // initially not visible + scene.add(this.axes); + this.axes.traverse(function(obj) { + obj.visible = false; + }); /** - * Behaves like THREE.OrbitControls, but uses right-handed coordinates and z as up vector. + * Handle the mousedown 3D event. * - * @constructor - * @param scene - the global scene to use - * @param camera - the camera to use - * @param userZoomSpeed (optional) - the speed for zooming - * @param userRotateSpeed (optional) - the speed for rotating - * @param autoRotate (optional) - if the orbit should auto rotate - * @param autoRotate (optional) - the speed for auto rotating + * @param event3D - the 3D event to handle */ - constructor(options) { - super(); - var that = this; - options = options || {}; - var scene = options.scene; - this.camera = options.camera; - this.center = new THREE$1.Vector3(); - this.userZoom = true; - this.userZoomSpeed = options.userZoomSpeed || 1.0; - this.userRotate = true; - this.userRotateSpeed = options.userRotateSpeed || 1.0; - this.autoRotate = options.autoRotate; - this.autoRotateSpeed = options.autoRotateSpeed || 2.0; + function onMouseDown(event3D) { + var event = event3D.domEvent; + event.preventDefault(); - // In ROS, z is pointing upwards - this.camera.up = new THREE$1.Vector3(0, 0, 1); + switch (event.button) { + case 0: + state = STATE.ROTATE; + rotateStart.set(event.clientX, event.clientY); + break; + case 1: + state = STATE.MOVE; - // internals - var pixelsPerRound = 1800; - var touchMoveThreshold = 10; - var rotateStart = new THREE$1.Vector2(); - var rotateEnd = new THREE$1.Vector2(); - var rotateDelta = new THREE$1.Vector2(); - var zoomStart = new THREE$1.Vector2(); - var zoomEnd = new THREE$1.Vector2(); - var zoomDelta = new THREE$1.Vector2(); - var moveStartCenter = new THREE$1.Vector3(); - var moveStartNormal = new THREE$1.Vector3(); - var moveStartPosition = new THREE$1.Vector3(); - var moveStartIntersection = new THREE$1.Vector3(); - var touchStartPosition = new Array(2); - var touchMoveVector = new Array(2); - this.phiDelta = 0; - this.thetaDelta = 0; - this.scale = 1; - this.lastPosition = new THREE$1.Vector3(); - // internal states - var STATE = { - NONE : -1, - ROTATE : 0, - ZOOM : 1, - MOVE : 2 - }; - var state = STATE.NONE; + moveStartNormal = new THREE.Vector3(0, 0, 1); + var rMat = new THREE.Matrix4().extractRotation(this.camera.matrix); + moveStartNormal.applyMatrix4(rMat); - // add the axes for the main coordinate frame - this.axes = new Axes({ - shaftRadius : 0.025, - headRadius : 0.07, - headLength : 0.2 - }); - // initially not visible - scene.add(this.axes); - this.axes.traverse(function(obj) { - obj.visible = false; - }); + moveStartCenter = that.center.clone(); + moveStartPosition = that.camera.position.clone(); + moveStartIntersection = intersectViewPlane(event3D.mouseRay, + moveStartCenter, + moveStartNormal); + break; + case 2: + state = STATE.ZOOM; + zoomStart.set(event.clientX, event.clientY); + break; + } - /** - * Handle the mousedown 3D event. - * - * @param event3D - the 3D event to handle - */ - function onMouseDown(event3D) { - var event = event3D.domEvent; - event.preventDefault(); + this.showAxes(); + } - switch (event.button) { - case 0: - state = STATE.ROTATE; - rotateStart.set(event.clientX, event.clientY); - break; - case 1: - state = STATE.MOVE; + /** + * Handle the mousemove 3D event. + * + * @param event3D - the 3D event to handle + */ + function onMouseMove(event3D) { + var event = event3D.domEvent; + if (state === STATE.ROTATE) { - moveStartNormal = new THREE$1.Vector3(0, 0, 1); - var rMat = new THREE$1.Matrix4().extractRotation(this.camera.matrix); - moveStartNormal.applyMatrix4(rMat); + rotateEnd.set(event.clientX, event.clientY); + rotateDelta.subVectors(rotateEnd, rotateStart); - moveStartCenter = that.center.clone(); - moveStartPosition = that.camera.position.clone(); - moveStartIntersection = intersectViewPlane(event3D.mouseRay, - moveStartCenter, - moveStartNormal); - break; - case 2: - state = STATE.ZOOM; - zoomStart.set(event.clientX, event.clientY); - break; - } + that.rotateLeft(2 * Math.PI * rotateDelta.x / pixelsPerRound * that.userRotateSpeed); + that.rotateUp(2 * Math.PI * rotateDelta.y / pixelsPerRound * that.userRotateSpeed); + rotateStart.copy(rotateEnd); this.showAxes(); - } + } else if (state === STATE.ZOOM) { + zoomEnd.set(event.clientX, event.clientY); + zoomDelta.subVectors(zoomEnd, zoomStart); - /** - * Handle the mousemove 3D event. - * - * @param event3D - the 3D event to handle - */ - function onMouseMove(event3D) { - var event = event3D.domEvent; - if (state === STATE.ROTATE) { - - rotateEnd.set(event.clientX, event.clientY); - rotateDelta.subVectors(rotateEnd, rotateStart); - - that.rotateLeft(2 * Math.PI * rotateDelta.x / pixelsPerRound * that.userRotateSpeed); - that.rotateUp(2 * Math.PI * rotateDelta.y / pixelsPerRound * that.userRotateSpeed); - - rotateStart.copy(rotateEnd); - this.showAxes(); - } else if (state === STATE.ZOOM) { - zoomEnd.set(event.clientX, event.clientY); - zoomDelta.subVectors(zoomEnd, zoomStart); - - if (zoomDelta.y > 0) { - that.zoomIn(); - } else { - that.zoomOut(); - } - - zoomStart.copy(zoomEnd); - this.showAxes(); - - } else if (state === STATE.MOVE) { - var intersection = intersectViewPlane(event3D.mouseRay, that.center, moveStartNormal); - - if (!intersection) { - return; - } - - var delta = new THREE$1.Vector3().subVectors(moveStartIntersection.clone(), intersection - .clone()); - - that.center.addVectors(moveStartCenter.clone(), delta.clone()); - that.camera.position.addVectors(moveStartPosition.clone(), delta.clone()); - that.update(); - that.camera.updateMatrixWorld(); - this.showAxes(); - } - } - - /** - * Used to track the movement during camera movement. - * - * @param mouseRay - the mouse ray to intersect with - * @param planeOrigin - the origin of the plane - * @param planeNormal - the normal of the plane - * @returns the intersection - */ - function intersectViewPlane(mouseRay, planeOrigin, planeNormal) { - - var vector = new THREE$1.Vector3(); - var intersection = new THREE$1.Vector3(); - - vector.subVectors(planeOrigin, mouseRay.origin); - var dot = mouseRay.direction.dot(planeNormal); - - // bail if ray and plane are parallel - if (Math.abs(dot) < mouseRay.precision) { - return null; - } - - // calc distance to plane - var scalar = planeNormal.dot(vector) / dot; - - intersection = mouseRay.direction.clone().multiplyScalar(scalar); - return intersection; - } - - /** - * Handle the mouseup 3D event. - * - * @param event3D - the 3D event to handle - */ - function onMouseUp(event3D) { - if (!that.userRotate) { - return; - } - - state = STATE.NONE; - } - - /** - * Handle the mousewheel 3D event. - * - * @param event3D - the 3D event to handle - */ - function onMouseWheel(event3D) { - if (!that.userZoom) { - return; - } - - var event = event3D.domEvent; - // wheelDelta --> Chrome, detail --> Firefox - var delta; - if (typeof (event.wheelDelta) !== 'undefined') { - delta = event.wheelDelta; - } else { - delta = -event.detail; - } - if (delta > 0) { + if (zoomDelta.y > 0) { that.zoomIn(); } else { that.zoomOut(); } + zoomStart.copy(zoomEnd); + this.showAxes(); + + } else if (state === STATE.MOVE) { + var intersection = intersectViewPlane(event3D.mouseRay, that.center, moveStartNormal); + + if (!intersection) { + return; + } + + var delta = new THREE.Vector3().subVectors(moveStartIntersection.clone(), intersection + .clone()); + + that.center.addVectors(moveStartCenter.clone(), delta.clone()); + that.camera.position.addVectors(moveStartPosition.clone(), delta.clone()); + that.update(); + that.camera.updateMatrixWorld(); this.showAxes(); } + } - /** - * Handle the touchdown 3D event. - * - * @param event3D - the 3D event to handle - */ - function onTouchDown(event3D) { - var event = event3D.domEvent; - switch (event.touches.length) { - case 1: - state = STATE.ROTATE; - rotateStart.set(event.touches[0].pageX - window.scrollX, - event.touches[0].pageY - window.scrollY); - break; - case 2: - state = STATE.NONE; - /* ready for move */ - moveStartNormal = new THREE$1.Vector3(0, 0, 1); - var rMat = new THREE$1.Matrix4().extractRotation(this.camera.matrix); - moveStartNormal.applyMatrix4(rMat); - moveStartCenter = that.center.clone(); - moveStartPosition = that.camera.position.clone(); - moveStartIntersection = intersectViewPlane(event3D.mouseRay, - moveStartCenter, - moveStartNormal); - touchStartPosition[0] = new THREE$1.Vector2(event.touches[0].pageX, - event.touches[0].pageY); - touchStartPosition[1] = new THREE$1.Vector2(event.touches[1].pageX, - event.touches[1].pageY); - touchMoveVector[0] = new THREE$1.Vector2(0, 0); - touchMoveVector[1] = new THREE$1.Vector2(0, 0); - break; + /** + * Used to track the movement during camera movement. + * + * @param mouseRay - the mouse ray to intersect with + * @param planeOrigin - the origin of the plane + * @param planeNormal - the normal of the plane + * @returns the intersection + */ + function intersectViewPlane(mouseRay, planeOrigin, planeNormal) { + + var vector = new THREE.Vector3(); + var intersection = new THREE.Vector3(); + + vector.subVectors(planeOrigin, mouseRay.origin); + var dot = mouseRay.direction.dot(planeNormal); + + // bail if ray and plane are parallel + if (Math.abs(dot) < mouseRay.precision) { + return null; + } + + // calc distance to plane + var scalar = planeNormal.dot(vector) / dot; + + intersection = mouseRay.direction.clone().multiplyScalar(scalar); + return intersection; + } + + /** + * Handle the mouseup 3D event. + * + * @param event3D - the 3D event to handle + */ + function onMouseUp(event3D) { + if (!that.userRotate) { + return; + } + + state = STATE.NONE; + } + + /** + * Handle the mousewheel 3D event. + * + * @param event3D - the 3D event to handle + */ + function onMouseWheel(event3D) { + if (!that.userZoom) { + return; + } + + var event = event3D.domEvent; + // wheelDelta --> Chrome, detail --> Firefox + var delta; + if (typeof (event.wheelDelta) !== 'undefined') { + delta = event.wheelDelta; + } else { + delta = -event.detail; + } + if (delta > 0) { + that.zoomIn(); + } else { + that.zoomOut(); + } + + this.showAxes(); + } + + /** + * Handle the touchdown 3D event. + * + * @param event3D - the 3D event to handle + */ + function onTouchDown(event3D) { + var event = event3D.domEvent; + switch (event.touches.length) { + case 1: + state = STATE.ROTATE; + rotateStart.set(event.touches[0].pageX - window.scrollX, + event.touches[0].pageY - window.scrollY); + break; + case 2: + state = STATE.NONE; + /* ready for move */ + moveStartNormal = new THREE.Vector3(0, 0, 1); + var rMat = new THREE.Matrix4().extractRotation(this.camera.matrix); + moveStartNormal.applyMatrix4(rMat); + moveStartCenter = that.center.clone(); + moveStartPosition = that.camera.position.clone(); + moveStartIntersection = intersectViewPlane(event3D.mouseRay, + moveStartCenter, + moveStartNormal); + touchStartPosition[0] = new THREE.Vector2(event.touches[0].pageX, + event.touches[0].pageY); + touchStartPosition[1] = new THREE.Vector2(event.touches[1].pageX, + event.touches[1].pageY); + touchMoveVector[0] = new THREE.Vector2(0, 0); + touchMoveVector[1] = new THREE.Vector2(0, 0); + break; + } + + this.showAxes(); + + event.preventDefault(); + } + + /** + * Handle the touchmove 3D event. + * + * @param event3D - the 3D event to handle + */ + function onTouchMove(event3D) { + var event = event3D.domEvent; + if (state === STATE.ROTATE) { + + rotateEnd.set(event.touches[0].pageX - window.scrollX, event.touches[0].pageY - window.scrollY); + rotateDelta.subVectors(rotateEnd, rotateStart); + + that.rotateLeft(2 * Math.PI * rotateDelta.x / pixelsPerRound * that.userRotateSpeed); + that.rotateUp(2 * Math.PI * rotateDelta.y / pixelsPerRound * that.userRotateSpeed); + + rotateStart.copy(rotateEnd); + this.showAxes(); + } else { + touchMoveVector[0].set(touchStartPosition[0].x - event.touches[0].pageX, + touchStartPosition[0].y - event.touches[0].pageY); + touchMoveVector[1].set(touchStartPosition[1].x - event.touches[1].pageX, + touchStartPosition[1].y - event.touches[1].pageY); + if (touchMoveVector[0].lengthSq() > touchMoveThreshold && + touchMoveVector[1].lengthSq() > touchMoveThreshold) { + touchStartPosition[0].set(event.touches[0].pageX, + event.touches[0].pageY); + touchStartPosition[1].set(event.touches[1].pageX, + event.touches[1].pageY); + if (touchMoveVector[0].dot(touchMoveVector[1]) > 0 && + state !== STATE.ZOOM) { + state = STATE.MOVE; + } else if (touchMoveVector[0].dot(touchMoveVector[1]) < 0 && + state !== STATE.MOVE) { + state = STATE.ZOOM; + } + if (state === STATE.ZOOM) { + var tmpVector = new THREE.Vector2(); + tmpVector.subVectors(touchStartPosition[0], + touchStartPosition[1]); + if (touchMoveVector[0].dot(tmpVector) < 0 && + touchMoveVector[1].dot(tmpVector) > 0) { + that.zoomOut(); + } else if (touchMoveVector[0].dot(tmpVector) > 0 && + touchMoveVector[1].dot(tmpVector) < 0) { + that.zoomIn(); + } + } + } + if (state === STATE.MOVE) { + var intersection = intersectViewPlane(event3D.mouseRay, + that.center, + moveStartNormal); + if (!intersection) { + return; + } + var delta = new THREE.Vector3().subVectors(moveStartIntersection.clone(), + intersection.clone()); + that.center.addVectors(moveStartCenter.clone(), delta.clone()); + that.camera.position.addVectors(moveStartPosition.clone(), delta.clone()); + that.update(); + that.camera.updateMatrixWorld(); } this.showAxes(); event.preventDefault(); } + } - /** - * Handle the touchmove 3D event. - * - * @param event3D - the 3D event to handle - */ - function onTouchMove(event3D) { - var event = event3D.domEvent; - if (state === STATE.ROTATE) { - - rotateEnd.set(event.touches[0].pageX - window.scrollX, event.touches[0].pageY - window.scrollY); - rotateDelta.subVectors(rotateEnd, rotateStart); - - that.rotateLeft(2 * Math.PI * rotateDelta.x / pixelsPerRound * that.userRotateSpeed); - that.rotateUp(2 * Math.PI * rotateDelta.y / pixelsPerRound * that.userRotateSpeed); - - rotateStart.copy(rotateEnd); - this.showAxes(); - } else { - touchMoveVector[0].set(touchStartPosition[0].x - event.touches[0].pageX, - touchStartPosition[0].y - event.touches[0].pageY); - touchMoveVector[1].set(touchStartPosition[1].x - event.touches[1].pageX, - touchStartPosition[1].y - event.touches[1].pageY); - if (touchMoveVector[0].lengthSq() > touchMoveThreshold && - touchMoveVector[1].lengthSq() > touchMoveThreshold) { - touchStartPosition[0].set(event.touches[0].pageX, - event.touches[0].pageY); - touchStartPosition[1].set(event.touches[1].pageX, - event.touches[1].pageY); - if (touchMoveVector[0].dot(touchMoveVector[1]) > 0 && - state !== STATE.ZOOM) { - state = STATE.MOVE; - } else if (touchMoveVector[0].dot(touchMoveVector[1]) < 0 && - state !== STATE.MOVE) { - state = STATE.ZOOM; - } - if (state === STATE.ZOOM) { - var tmpVector = new THREE$1.Vector2(); - tmpVector.subVectors(touchStartPosition[0], - touchStartPosition[1]); - if (touchMoveVector[0].dot(tmpVector) < 0 && - touchMoveVector[1].dot(tmpVector) > 0) { - that.zoomOut(); - } else if (touchMoveVector[0].dot(tmpVector) > 0 && - touchMoveVector[1].dot(tmpVector) < 0) { - that.zoomIn(); - } - } - } - if (state === STATE.MOVE) { - var intersection = intersectViewPlane(event3D.mouseRay, - that.center, - moveStartNormal); - if (!intersection) { - return; - } - var delta = new THREE$1.Vector3().subVectors(moveStartIntersection.clone(), - intersection.clone()); - that.center.addVectors(moveStartCenter.clone(), delta.clone()); - that.camera.position.addVectors(moveStartPosition.clone(), delta.clone()); - that.update(); - that.camera.updateMatrixWorld(); - } - - this.showAxes(); - - event.preventDefault(); - } + function onTouchEnd(event3D) { + var event = event3D.domEvent; + if (event.touches.length === 1 && + state !== STATE.ROTATE) { + state = STATE.ROTATE; + rotateStart.set(event.touches[0].pageX - window.scrollX, + event.touches[0].pageY - window.scrollY); } + } - function onTouchEnd(event3D) { - var event = event3D.domEvent; - if (event.touches.length === 1 && - state !== STATE.ROTATE) { - state = STATE.ROTATE; - rotateStart.set(event.touches[0].pageX - window.scrollX, - event.touches[0].pageY - window.scrollY); - } - else { - state = STATE.NONE; - } - } - - // add event listeners - this.addEventListener('mousedown', onMouseDown); - this.addEventListener('mouseup', onMouseUp); - this.addEventListener('mousemove', onMouseMove); - this.addEventListener('touchstart', onTouchDown); - this.addEventListener('touchmove', onTouchMove); - this.addEventListener('touchend', onTouchEnd); - // Chrome/Firefox have different events here - this.addEventListener('mousewheel', onMouseWheel); - this.addEventListener('DOMMouseScroll', onMouseWheel); - }; - - /** - * Display the main axes for 1 second. - */ - showAxes() { - var that = this; - - this.axes.traverse(function(obj) { - obj.visible = true; - }); - if (this.hideTimeout) { - clearTimeout(this.hideTimeout); - } - this.hideTimeout = setTimeout(function() { - that.axes.traverse(function(obj) { - obj.visible = false; - }); - that.hideTimeout = false; - }, 1000); - }; - - /** - * Rotate the camera to the left by the given angle. - * - * @param angle (optional) - the angle to rotate by - */ - rotateLeft(angle) { - if (angle === undefined) { - angle = 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; - } - this.thetaDelta -= angle; - }; - - /** - * Rotate the camera to the right by the given angle. - * - * @param angle (optional) - the angle to rotate by - */ - rotateRight(angle) { - if (angle === undefined) { - angle = 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; - } - this.thetaDelta += angle; - }; - - /** - * Rotate the camera up by the given angle. - * - * @param angle (optional) - the angle to rotate by - */ - rotateUp(angle) { - if (angle === undefined) { - angle = 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; - } - this.phiDelta -= angle; - }; - - /** - * Rotate the camera down by the given angle. - * - * @param angle (optional) - the angle to rotate by - */ - rotateDown(angle) { - if (angle === undefined) { - angle = 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; - } - this.phiDelta += angle; - }; - - /** - * Zoom in by the given scale. - * - * @param zoomScale (optional) - the scale to zoom in by - */ - zoomIn(zoomScale) { - if (zoomScale === undefined) { - zoomScale = Math.pow(0.95, this.userZoomSpeed); - } - this.scale /= zoomScale; - }; - - /** - * Zoom out by the given scale. - * - * @param zoomScale (optional) - the scale to zoom in by - */ - zoomOut(zoomScale) { - if (zoomScale === undefined) { - zoomScale = Math.pow(0.95, this.userZoomSpeed); - } - this.scale *= zoomScale; - }; - - /** - * Update the camera to the current settings. - */ - update() { - // x->y, y->z, z->x - var position = this.camera.position; - var offset = position.clone().sub(this.center); - - // angle from z-axis around y-axis - var theta = Math.atan2(offset.y, offset.x); - - // angle from y-axis - var phi = Math.atan2(Math.sqrt(offset.y * offset.y + offset.x * offset.x), offset.z); - - if (this.autoRotate) { - this.rotateLeft(2 * Math.PI / 60 / 60 * this.autoRotateSpeed); - } - - theta += this.thetaDelta; - phi += this.phiDelta; - - // restrict phi to be between EPS and PI-EPS - var eps = 0.000001; - phi = Math.max(eps, Math.min(Math.PI - eps, phi)); - - var radius = offset.length(); - offset.set( - radius * Math.sin(phi) * Math.cos(theta), - radius * Math.sin(phi) * Math.sin(theta), - radius * Math.cos(phi) - ); - offset.multiplyScalar(this.scale); - - position.copy(this.center).add(offset); - - this.camera.lookAt(this.center); - - radius = offset.length(); - this.axes.position.copy(this.center); - this.axes.scale.set(radius * 0.05, radius * 0.05, radius * 0.05); - this.axes.updateMatrixWorld(true); - - this.thetaDelta = 0; - this.phiDelta = 0; - this.scale = 1; - - if (this.lastPosition.distanceTo(this.camera.position) > 0) { - this.dispatchEvent({ - type : 'change' - }); - this.lastPosition.copy(this.camera.position); - } - }; -} + // add event listeners + this.addEventListener('mousedown', onMouseDown); + this.addEventListener('mouseup', onMouseUp); + this.addEventListener('mousemove', onMouseMove); + this.addEventListener('touchstart', onTouchDown); + this.addEventListener('touchmove', onTouchMove); + this.addEventListener('touchend', onTouchEnd); + // Chrome/Firefox have different events here + this.addEventListener('mousewheel', onMouseWheel); + this.addEventListener('DOMMouseScroll', onMouseWheel); +}; /** - * @author David Gossow - dgossow@willowgarage.com - * @author Russell Toris - rctoris@wpi.edu - * @author Jihoon Lee - jihoonlee.in@gmail.com + * Display the main axes for 1 second. */ +ROS3D.OrbitControls.prototype.showAxes = function() { + var that = this; -class Viewer { - - /** - * A Viewer can be used to render an interactive 3D scene to a HTML5 canvas. - * - * @constructor - * @param options - object with following keys: - * - * * divID - the ID of the div to place the viewer in - * * width - the initial width, in pixels, of the canvas - * * height - the initial height, in pixels, of the canvas - * * background (optional) - the color to render the background, like '#efefef' - * * alpha (optional) - the alpha of the background - * * antialias (optional) - if antialiasing should be used - * * intensity (optional) - the lighting intensity setting to use - * * cameraPosition (optional) - the starting position of the camera - */ - constructor(options) { - options = options || {}; - var divID = options.divID; - var width = options.width; - var height = options.height; - var background = options.background || '#111111'; - var antialias = options.antialias; - var intensity = options.intensity || 0.66; - var near = options.near || 0.01; - var far = options.far || 1000; - var alpha = options.alpha || 1.0; - var cameraPosition = options.cameraPose || { - x : 3, - y : 3, - z : 3 - }; - var cameraZoomSpeed = options.cameraZoomSpeed || 0.5; - - // create the canvas to render to - this.renderer = new THREE$1.WebGLRenderer({ - antialias : antialias, - alpha: true + this.axes.traverse(function(obj) { + obj.visible = true; + }); + if (this.hideTimeout) { + clearTimeout(this.hideTimeout); + } + this.hideTimeout = setTimeout(function() { + that.axes.traverse(function(obj) { + obj.visible = false; }); - this.renderer.setClearColor(parseInt(background.replace('#', '0x'), 16), alpha); - this.renderer.sortObjects = false; - this.renderer.setSize(width, height); - this.renderer.shadowMap.enabled = false; - this.renderer.autoClear = false; + that.hideTimeout = false; + }, 1000); +}; - // create the global scene - this.scene = new THREE$1.Scene(); +/** + * Rotate the camera to the left by the given angle. + * + * @param angle (optional) - the angle to rotate by + */ +ROS3D.OrbitControls.prototype.rotateLeft = function(angle) { + if (angle === undefined) { + angle = 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; + } + this.thetaDelta -= angle; +}; - // create the global camera - this.camera = new THREE$1.PerspectiveCamera(40, width / height, near, far); - this.camera.position.x = cameraPosition.x; - this.camera.position.y = cameraPosition.y; - this.camera.position.z = cameraPosition.z; - // add controls to the camera - this.cameraControls = new OrbitControls({ - scene : this.scene, - camera : this.camera +/** + * Rotate the camera to the right by the given angle. + * + * @param angle (optional) - the angle to rotate by + */ +ROS3D.OrbitControls.prototype.rotateRight = function(angle) { + if (angle === undefined) { + angle = 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; + } + this.thetaDelta += angle; +}; + +/** + * Rotate the camera up by the given angle. + * + * @param angle (optional) - the angle to rotate by + */ +ROS3D.OrbitControls.prototype.rotateUp = function(angle) { + if (angle === undefined) { + angle = 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; + } + this.phiDelta -= angle; +}; + +/** + * Rotate the camera down by the given angle. + * + * @param angle (optional) - the angle to rotate by + */ +ROS3D.OrbitControls.prototype.rotateDown = function(angle) { + if (angle === undefined) { + angle = 2 * Math.PI / 60 / 60 * this.autoRotateSpeed; + } + this.phiDelta += angle; +}; + +/** + * Zoom in by the given scale. + * + * @param zoomScale (optional) - the scale to zoom in by + */ +ROS3D.OrbitControls.prototype.zoomIn = function(zoomScale) { + if (zoomScale === undefined) { + zoomScale = Math.pow(0.95, this.userZoomSpeed); + } + this.scale /= zoomScale; +}; + +/** + * Zoom out by the given scale. + * + * @param zoomScale (optional) - the scale to zoom in by + */ +ROS3D.OrbitControls.prototype.zoomOut = function(zoomScale) { + if (zoomScale === undefined) { + zoomScale = Math.pow(0.95, this.userZoomSpeed); + } + this.scale *= zoomScale; +}; + +/** + * Update the camera to the current settings. + */ +ROS3D.OrbitControls.prototype.update = function() { + // x->y, y->z, z->x + var position = this.camera.position; + var offset = position.clone().sub(this.center); + + // angle from z-axis around y-axis + var theta = Math.atan2(offset.y, offset.x); + + // angle from y-axis + var phi = Math.atan2(Math.sqrt(offset.y * offset.y + offset.x * offset.x), offset.z); + + if (this.autoRotate) { + this.rotateLeft(2 * Math.PI / 60 / 60 * this.autoRotateSpeed); + } + + theta += this.thetaDelta; + phi += this.phiDelta; + + // restrict phi to be between EPS and PI-EPS + var eps = 0.000001; + phi = Math.max(eps, Math.min(Math.PI - eps, phi)); + + var radius = offset.length(); + offset.y = radius * Math.sin(phi) * Math.sin(theta); + offset.z = radius * Math.cos(phi); + offset.x = radius * Math.sin(phi) * Math.cos(theta); + offset.multiplyScalar(this.scale); + + position.copy(this.center).add(offset); + + this.camera.lookAt(this.center); + + radius = offset.length(); + this.axes.position = this.center.clone(); + this.axes.scale.x = this.axes.scale.y = this.axes.scale.z = radius * 0.05; + this.axes.updateMatrixWorld(true); + + this.thetaDelta = 0; + this.phiDelta = 0; + this.scale = 1; + + if (this.lastPosition.distanceTo(this.camera.position) > 0) { + this.dispatchEvent({ + type : 'change' }); - this.cameraControls.userZoomSpeed = cameraZoomSpeed; + this.lastPosition.copy(this.camera.position); + } +}; - // lights - this.scene.add(new THREE$1.AmbientLight(0x555555)); - this.directionalLight = new THREE$1.DirectionalLight(0xffffff, intensity); - this.scene.add(this.directionalLight); - - // propagates mouse events to three.js objects - this.selectableObjects = new THREE$1.Object3D(); - this.scene.add(this.selectableObjects); - var mouseHandler = new MouseHandler({ - renderer : this.renderer, - camera : this.camera, - rootObject : this.selectableObjects, - fallbackTarget : this.cameraControls - }); - - // highlights the receiver of mouse events - this.highlighter = new Highlighter({ - mouseHandler : mouseHandler - }); - - this.stopped = true; - this.animationRequestId = undefined; - - // add the renderer to the page - document.getElementById(divID).appendChild(this.renderer.domElement); - - // begin the render loop - this.start(); - }; - - /** - * Start the render loop - */ - start(){ - this.stopped = false; - this.draw(); - }; - - /** - * Renders the associated scene to the viewer. - */ - draw(){ - if(this.stopped){ - // Do nothing if stopped - return; - } - - // update the controls - this.cameraControls.update(); - - // put light to the top-left of the camera - // BUG: position is a read-only property of DirectionalLight, - // attempting to assign to it either does nothing or throws an error. - //this.directionalLight.position = this.camera.localToWorld(new THREE.Vector3(-1, 1, 0)); - this.directionalLight.position.normalize(); - - // set the scene - this.renderer.clear(true, true, true); - this.renderer.render(this.scene, this.camera); - this.highlighter.renderHighlights(this.scene, this.renderer, this.camera); - - // draw the frame - this.animationRequestId = requestAnimationFrame(this.draw.bind(this)); - }; - - /** - * Stop the render loop - */ - stop(){ - if(!this.stopped){ - // Stop animation render loop - cancelAnimationFrame(this.animationRequestId); - } - this.stopped = true; - }; - - /** - * Add the given THREE Object3D to the global scene in the viewer. - * - * @param object - the THREE Object3D to add - * @param selectable (optional) - if the object should be added to the selectable list - */ - addObject(object, selectable) { - if (selectable) { - this.selectableObjects.add(object); - } else { - this.scene.add(object); - } - }; - - /** - * Resize 3D viewer - * - * @param width - new width value - * @param height - new height value - */ - resize(width, height) { - this.camera.aspect = width / height; - this.camera.updateProjectionMatrix(); - this.renderer.setSize(width, height); - }; -} - -exports.REVISION = REVISION$1; -exports.MARKER_ARROW = MARKER_ARROW; -exports.MARKER_CUBE = MARKER_CUBE; -exports.MARKER_SPHERE = MARKER_SPHERE; -exports.MARKER_CYLINDER = MARKER_CYLINDER; -exports.MARKER_LINE_STRIP = MARKER_LINE_STRIP; -exports.MARKER_LINE_LIST = MARKER_LINE_LIST; -exports.MARKER_CUBE_LIST = MARKER_CUBE_LIST; -exports.MARKER_SPHERE_LIST = MARKER_SPHERE_LIST; -exports.MARKER_POINTS = MARKER_POINTS; -exports.MARKER_TEXT_VIEW_FACING = MARKER_TEXT_VIEW_FACING; -exports.MARKER_MESH_RESOURCE = MARKER_MESH_RESOURCE; -exports.MARKER_TRIANGLE_LIST = MARKER_TRIANGLE_LIST; -exports.INTERACTIVE_MARKER_KEEP_ALIVE = INTERACTIVE_MARKER_KEEP_ALIVE; -exports.INTERACTIVE_MARKER_POSE_UPDATE = INTERACTIVE_MARKER_POSE_UPDATE; -exports.INTERACTIVE_MARKER_MENU_SELECT = INTERACTIVE_MARKER_MENU_SELECT; -exports.INTERACTIVE_MARKER_BUTTON_CLICK = INTERACTIVE_MARKER_BUTTON_CLICK; -exports.INTERACTIVE_MARKER_MOUSE_DOWN = INTERACTIVE_MARKER_MOUSE_DOWN; -exports.INTERACTIVE_MARKER_MOUSE_UP = INTERACTIVE_MARKER_MOUSE_UP; -exports.INTERACTIVE_MARKER_NONE = INTERACTIVE_MARKER_NONE; -exports.INTERACTIVE_MARKER_MENU = INTERACTIVE_MARKER_MENU; -exports.INTERACTIVE_MARKER_BUTTON = INTERACTIVE_MARKER_BUTTON; -exports.INTERACTIVE_MARKER_MOVE_AXIS = INTERACTIVE_MARKER_MOVE_AXIS; -exports.INTERACTIVE_MARKER_MOVE_PLANE = INTERACTIVE_MARKER_MOVE_PLANE; -exports.INTERACTIVE_MARKER_ROTATE_AXIS = INTERACTIVE_MARKER_ROTATE_AXIS; -exports.INTERACTIVE_MARKER_MOVE_ROTATE = INTERACTIVE_MARKER_MOVE_ROTATE; -exports.INTERACTIVE_MARKER_INHERIT = INTERACTIVE_MARKER_INHERIT; -exports.INTERACTIVE_MARKER_FIXED = INTERACTIVE_MARKER_FIXED; -exports.INTERACTIVE_MARKER_VIEW_FACING = INTERACTIVE_MARKER_VIEW_FACING; -exports.makeColorMaterial = makeColorMaterial; -exports.intersectPlane = intersectPlane; -exports.findClosestPoint = findClosestPoint; -exports.closestAxisPoint = closestAxisPoint; -exports.DepthCloud = DepthCloud; -exports.InteractiveMarker = InteractiveMarker; -exports.InteractiveMarkerClient = InteractiveMarkerClient; -exports.InteractiveMarkerControl = InteractiveMarkerControl; -exports.InteractiveMarkerHandle = InteractiveMarkerHandle; -exports.InteractiveMarkerMenu = InteractiveMarkerMenu; -exports.Marker = Marker; -exports.MarkerArrayClient = MarkerArrayClient; -exports.MarkerClient = MarkerClient; -exports.Arrow = Arrow; -exports.Arrow2 = Arrow2; -exports.Axes = Axes; -exports.Grid = Grid; -exports.MeshResource = MeshResource; -exports.TriangleList = TriangleList; -exports.OccupancyGrid = OccupancyGrid; -exports.OccupancyGridClient = OccupancyGridClient; -exports.Odometry = Odometry; -exports.Path = Path$1; -exports.Point = Point; -exports.Polygon = Polygon; -exports.Pose = Pose; -exports.PoseArray = PoseArray; -exports.PoseWithCovariance = PoseWithCovariance; -exports.LaserScan = LaserScan; -exports.Points = Points$1; -exports.PointCloud2 = PointCloud2; -exports.Urdf = Urdf; -exports.UrdfClient = UrdfClient; -exports.Highlighter = Highlighter; -exports.MouseHandler = MouseHandler; -exports.OrbitControls = OrbitControls; -exports.SceneNode = SceneNode; -exports.Viewer = Viewer; - -return exports; - -}({},ROSLIB)); +THREE.EventDispatcher.prototype.apply( ROS3D.OrbitControls.prototype ); diff --git a/clover/www/js/roslib.js b/clover/www/js/roslib.js index 2193da84..8f528bee 100644 --- a/clover/www/js/roslib.js +++ b/clover/www/js/roslib.js @@ -1,4 +1,413 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +(function(global, undefined) { "use strict"; +var POW_2_24 = Math.pow(2, -24), + POW_2_32 = Math.pow(2, 32), + POW_2_53 = Math.pow(2, 53); + +function encode(value) { + var data = new ArrayBuffer(256); + var dataView = new DataView(data); + var lastLength; + var offset = 0; + + function ensureSpace(length) { + var newByteLength = data.byteLength; + var requiredLength = offset + length; + while (newByteLength < requiredLength) + newByteLength *= 2; + if (newByteLength !== data.byteLength) { + var oldDataView = dataView; + data = new ArrayBuffer(newByteLength); + dataView = new DataView(data); + var uint32count = (offset + 3) >> 2; + for (var i = 0; i < uint32count; ++i) + dataView.setUint32(i * 4, oldDataView.getUint32(i * 4)); + } + + lastLength = length; + return dataView; + } + function write() { + offset += lastLength; + } + function writeFloat64(value) { + write(ensureSpace(8).setFloat64(offset, value)); + } + function writeUint8(value) { + write(ensureSpace(1).setUint8(offset, value)); + } + function writeUint8Array(value) { + var dataView = ensureSpace(value.length); + for (var i = 0; i < value.length; ++i) + dataView.setUint8(offset + i, value[i]); + write(); + } + function writeUint16(value) { + write(ensureSpace(2).setUint16(offset, value)); + } + function writeUint32(value) { + write(ensureSpace(4).setUint32(offset, value)); + } + function writeUint64(value) { + var low = value % POW_2_32; + var high = (value - low) / POW_2_32; + var dataView = ensureSpace(8); + dataView.setUint32(offset, high); + dataView.setUint32(offset + 4, low); + write(); + } + function writeTypeAndLength(type, length) { + if (length < 24) { + writeUint8(type << 5 | length); + } else if (length < 0x100) { + writeUint8(type << 5 | 24); + writeUint8(length); + } else if (length < 0x10000) { + writeUint8(type << 5 | 25); + writeUint16(length); + } else if (length < 0x100000000) { + writeUint8(type << 5 | 26); + writeUint32(length); + } else { + writeUint8(type << 5 | 27); + writeUint64(length); + } + } + + function encodeItem(value) { + var i; + + if (value === false) + return writeUint8(0xf4); + if (value === true) + return writeUint8(0xf5); + if (value === null) + return writeUint8(0xf6); + if (value === undefined) + return writeUint8(0xf7); + + switch (typeof value) { + case "number": + if (Math.floor(value) === value) { + if (0 <= value && value <= POW_2_53) + return writeTypeAndLength(0, value); + if (-POW_2_53 <= value && value < 0) + return writeTypeAndLength(1, -(value + 1)); + } + writeUint8(0xfb); + return writeFloat64(value); + + case "string": + var utf8data = []; + for (i = 0; i < value.length; ++i) { + var charCode = value.charCodeAt(i); + if (charCode < 0x80) { + utf8data.push(charCode); + } else if (charCode < 0x800) { + utf8data.push(0xc0 | charCode >> 6); + utf8data.push(0x80 | charCode & 0x3f); + } else if (charCode < 0xd800) { + utf8data.push(0xe0 | charCode >> 12); + utf8data.push(0x80 | (charCode >> 6) & 0x3f); + utf8data.push(0x80 | charCode & 0x3f); + } else { + charCode = (charCode & 0x3ff) << 10; + charCode |= value.charCodeAt(++i) & 0x3ff; + charCode += 0x10000; + + utf8data.push(0xf0 | charCode >> 18); + utf8data.push(0x80 | (charCode >> 12) & 0x3f); + utf8data.push(0x80 | (charCode >> 6) & 0x3f); + utf8data.push(0x80 | charCode & 0x3f); + } + } + + writeTypeAndLength(3, utf8data.length); + return writeUint8Array(utf8data); + + default: + var length; + if (Array.isArray(value)) { + length = value.length; + writeTypeAndLength(4, length); + for (i = 0; i < length; ++i) + encodeItem(value[i]); + } else if (value instanceof Uint8Array) { + writeTypeAndLength(2, value.length); + writeUint8Array(value); + } else { + var keys = Object.keys(value); + length = keys.length; + writeTypeAndLength(5, length); + for (i = 0; i < length; ++i) { + var key = keys[i]; + encodeItem(key); + encodeItem(value[key]); + } + } + } + } + + encodeItem(value); + + if ("slice" in data) + return data.slice(0, offset); + + var ret = new ArrayBuffer(offset); + var retView = new DataView(ret); + for (var i = 0; i < offset; ++i) + retView.setUint8(i, dataView.getUint8(i)); + return ret; +} + +function decode(data, tagger, simpleValue) { + var dataView = new DataView(data); + var offset = 0; + + if (typeof tagger !== "function") + tagger = function(value) { return value; }; + if (typeof simpleValue !== "function") + simpleValue = function() { return undefined; }; + + function read(value, length) { + offset += length; + return value; + } + function readArrayBuffer(length) { + return read(new Uint8Array(data, offset, length), length); + } + function readFloat16() { + var tempArrayBuffer = new ArrayBuffer(4); + var tempDataView = new DataView(tempArrayBuffer); + var value = readUint16(); + + var sign = value & 0x8000; + var exponent = value & 0x7c00; + var fraction = value & 0x03ff; + + if (exponent === 0x7c00) + exponent = 0xff << 10; + else if (exponent !== 0) + exponent += (127 - 15) << 10; + else if (fraction !== 0) + return fraction * POW_2_24; + + tempDataView.setUint32(0, sign << 16 | exponent << 13 | fraction << 13); + return tempDataView.getFloat32(0); + } + function readFloat32() { + return read(dataView.getFloat32(offset), 4); + } + function readFloat64() { + return read(dataView.getFloat64(offset), 8); + } + function readUint8() { + return read(dataView.getUint8(offset), 1); + } + function readUint16() { + return read(dataView.getUint16(offset), 2); + } + function readUint32() { + return read(dataView.getUint32(offset), 4); + } + function readUint64() { + return readUint32() * POW_2_32 + readUint32(); + } + function readBreak() { + if (dataView.getUint8(offset) !== 0xff) + return false; + offset += 1; + return true; + } + function readLength(additionalInformation) { + if (additionalInformation < 24) + return additionalInformation; + if (additionalInformation === 24) + return readUint8(); + if (additionalInformation === 25) + return readUint16(); + if (additionalInformation === 26) + return readUint32(); + if (additionalInformation === 27) + return readUint64(); + if (additionalInformation === 31) + return -1; + throw "Invalid length encoding"; + } + function readIndefiniteStringLength(majorType) { + var initialByte = readUint8(); + if (initialByte === 0xff) + return -1; + var length = readLength(initialByte & 0x1f); + if (length < 0 || (initialByte >> 5) !== majorType) + throw "Invalid indefinite length element"; + return length; + } + + function appendUtf16data(utf16data, length) { + for (var i = 0; i < length; ++i) { + var value = readUint8(); + if (value & 0x80) { + if (value < 0xe0) { + value = (value & 0x1f) << 6 + | (readUint8() & 0x3f); + length -= 1; + } else if (value < 0xf0) { + value = (value & 0x0f) << 12 + | (readUint8() & 0x3f) << 6 + | (readUint8() & 0x3f); + length -= 2; + } else { + value = (value & 0x0f) << 18 + | (readUint8() & 0x3f) << 12 + | (readUint8() & 0x3f) << 6 + | (readUint8() & 0x3f); + length -= 3; + } + } + + if (value < 0x10000) { + utf16data.push(value); + } else { + value -= 0x10000; + utf16data.push(0xd800 | (value >> 10)); + utf16data.push(0xdc00 | (value & 0x3ff)); + } + } + } + + function decodeItem() { + var initialByte = readUint8(); + var majorType = initialByte >> 5; + var additionalInformation = initialByte & 0x1f; + var i; + var length; + + if (majorType === 7) { + switch (additionalInformation) { + case 25: + return readFloat16(); + case 26: + return readFloat32(); + case 27: + return readFloat64(); + } + } + + length = readLength(additionalInformation); + if (length < 0 && (majorType < 2 || 6 < majorType)) + throw "Invalid length"; + + switch (majorType) { + case 0: + return length; + case 1: + return -1 - length; + case 2: + if (length < 0) { + var elements = []; + var fullArrayLength = 0; + while ((length = readIndefiniteStringLength(majorType)) >= 0) { + fullArrayLength += length; + elements.push(readArrayBuffer(length)); + } + var fullArray = new Uint8Array(fullArrayLength); + var fullArrayOffset = 0; + for (i = 0; i < elements.length; ++i) { + fullArray.set(elements[i], fullArrayOffset); + fullArrayOffset += elements[i].length; + } + return fullArray; + } + return readArrayBuffer(length); + case 3: + var utf16data = []; + if (length < 0) { + while ((length = readIndefiniteStringLength(majorType)) >= 0) + appendUtf16data(utf16data, length); + } else + appendUtf16data(utf16data, length); + return String.fromCharCode.apply(null, utf16data); + case 4: + var retArray; + if (length < 0) { + retArray = []; + while (!readBreak()) + retArray.push(decodeItem()); + } else { + retArray = new Array(length); + for (i = 0; i < length; ++i) + retArray[i] = decodeItem(); + } + return retArray; + case 5: + var retObject = {}; + for (i = 0; i < length || length < 0 && !readBreak(); ++i) { + var key = decodeItem(); + retObject[key] = decodeItem(); + } + return retObject; + case 6: + return tagger(decodeItem(), length); + case 7: + switch (length) { + case 20: + return false; + case 21: + return true; + case 22: + return null; + case 23: + return undefined; + default: + return simpleValue(length); + } + } + } + + var ret = decodeItem(); + if (offset !== data.byteLength) + throw "Remaining bytes"; + return ret; +} + +var obj = { encode: encode, decode: decode }; + +if (typeof define === "function" && define.amd) + define("cbor/cbor", obj); +else if (typeof module !== 'undefined' && module.exports) + module.exports = obj; +else if (!global.CBOR) + global.CBOR = obj; + +})(this); + +},{}],2:[function(require,module,exports){ +(function (process){ /*! * EventEmitter2 * https://github.com/hij1nx/EventEmitter2 @@ -25,7 +434,8 @@ this._conf = conf; conf.delimiter && (this.delimiter = conf.delimiter); - this._events.maxListeners = conf.maxListeners !== undefined ? conf.maxListeners : defaultMaxListeners; + this._maxListeners = conf.maxListeners !== undefined ? conf.maxListeners : defaultMaxListeners; + conf.wildcard && (this.wildcard = conf.wildcard); conf.newListener && (this.newListener = conf.newListener); conf.verboseMemoryLeak && (this.verboseMemoryLeak = conf.verboseMemoryLeak); @@ -34,24 +444,31 @@ this.listenerTree = {}; } } else { - this._events.maxListeners = defaultMaxListeners; + this._maxListeners = defaultMaxListeners; } } function logPossibleMemoryLeak(count, eventName) { var errorMsg = '(node) warning: possible EventEmitter memory ' + - 'leak detected. %d listeners added. ' + + 'leak detected. ' + count + ' listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.'; if(this.verboseMemoryLeak){ - errorMsg += ' Event name: %s.'; - console.error(errorMsg, count, eventName); - } else { - console.error(errorMsg, count); + errorMsg += ' Event name: ' + eventName + '.'; } - if (console.trace){ - console.trace(); + if(typeof process !== 'undefined' && process.emitWarning){ + var e = new Error(errorMsg); + e.name = 'MaxListenersExceededWarning'; + e.emitter = this; + e.count = count; + process.emitWarning(e); + } else { + console.error(errorMsg); + + if (console.trace){ + console.trace(); + } } } @@ -212,8 +629,8 @@ if ( !tree._listeners.warned && - this._events.maxListeners > 0 && - tree._listeners.length > this._events.maxListeners + this._maxListeners > 0 && + tree._listeners.length > this._maxListeners ) { tree._listeners.warned = true; logPossibleMemoryLeak.call(this, tree._listeners.length, name); @@ -237,8 +654,7 @@ EventEmitter.prototype.setMaxListeners = function(n) { if (n !== undefined) { - this._events || init.call(this); - this._events.maxListeners = n; + this._maxListeners = n; if (!this._conf) this._conf = {}; this._conf.maxListeners = n; } @@ -246,12 +662,29 @@ EventEmitter.prototype.event = ''; + EventEmitter.prototype.once = function(event, fn) { - this.many(event, 1, fn); + return this._once(event, fn, false); + }; + + EventEmitter.prototype.prependOnceListener = function(event, fn) { + return this._once(event, fn, true); + }; + + EventEmitter.prototype._once = function(event, fn, prepend) { + this._many(event, 1, fn, prepend); return this; }; EventEmitter.prototype.many = function(event, ttl, fn) { + return this._many(event, ttl, fn, false); + } + + EventEmitter.prototype.prependMany = function(event, ttl, fn) { + return this._many(event, ttl, fn, true); + } + + EventEmitter.prototype._many = function(event, ttl, fn, prepend) { var self = this; if (typeof fn !== 'function') { @@ -262,12 +695,12 @@ if (--ttl === 0) { self.off(event, listener); } - fn.apply(this, arguments); + return fn.apply(this, arguments); } listener._origin = fn; - this.on(event, listener); + this._on(event, listener, prepend); return self; }; @@ -443,6 +876,7 @@ promises.push(handler.apply(this, args)); } } else if (handler && handler.length) { + handler = handler.slice(); if (al > 3) { args = new Array(al - 1); for (j = 1; j < al; j++) args[j - 1] = arguments[j]; @@ -475,8 +909,45 @@ }; EventEmitter.prototype.on = function(type, listener) { + return this._on(type, listener, false); + }; + + EventEmitter.prototype.prependListener = function(type, listener) { + return this._on(type, listener, true); + }; + + EventEmitter.prototype.onAny = function(fn) { + return this._onAny(fn, false); + }; + + EventEmitter.prototype.prependAny = function(fn) { + return this._onAny(fn, true); + }; + + EventEmitter.prototype.addListener = EventEmitter.prototype.on; + + EventEmitter.prototype._onAny = function(fn, prepend){ + if (typeof fn !== 'function') { + throw new Error('onAny only accepts instances of Function'); + } + + if (!this._all) { + this._all = []; + } + + // Add the function to the event listener collection. + if(prepend){ + this._all.unshift(fn); + }else{ + this._all.push(fn); + } + + return this; + } + + EventEmitter.prototype._on = function(type, listener, prepend) { if (typeof type === 'function') { - this.onAny(type); + this._onAny(type, listener); return this; } @@ -504,14 +975,18 @@ this._events[type] = [this._events[type]]; } - // If we've already got an array, just append. - this._events[type].push(listener); + // If we've already got an array, just add + if(prepend){ + this._events[type].unshift(listener); + }else{ + this._events[type].push(listener); + } // Check for listener leak if ( !this._events[type].warned && - this._events.maxListeners > 0 && - this._events[type].length > this._events.maxListeners + this._maxListeners > 0 && + this._events[type].length > this._maxListeners ) { this._events[type].warned = true; logPossibleMemoryLeak.call(this, this._events[type].length, type); @@ -519,23 +994,7 @@ } return this; - }; - - EventEmitter.prototype.onAny = function(fn) { - if (typeof fn !== 'function') { - throw new Error('onAny only accepts instances of Function'); - } - - if (!this._all) { - this._all = []; - } - - // Add the function to the event listener collection. - this._all.push(fn); - return this; - }; - - EventEmitter.prototype.addListener = EventEmitter.prototype.on; + } EventEmitter.prototype.off = function(type, listener) { if (typeof listener !== 'function') { @@ -692,6 +1151,10 @@ return this._events[type]; }; + EventEmitter.prototype.eventNames = function(){ + return Object.keys(this._events); + } + EventEmitter.prototype.listenerCount = function(type) { return this.listeners(type).length; }; @@ -722,7 +1185,8 @@ } }(); -},{}],2:[function(require,module,exports){ +}).call(this,require('_process')) +},{"_process":4}],3:[function(require,module,exports){ /* object-assign (c) Sindre Sorhus @@ -814,7 +1278,193 @@ module.exports = shouldUseNative() ? Object.assign : function (target, source) { return to; }; -},{}],3:[function(require,module,exports){ +},{}],4:[function(require,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],5:[function(require,module,exports){ /** * @fileOverview * @author Russell Toris - rctoris@wpi.edu @@ -826,7 +1476,7 @@ module.exports = shouldUseNative() ? Object.assign : function (target, source) { * If you use nodejs, this is the variable you get when you require('roslib') */ var ROSLIB = this.ROSLIB || { - REVISION : '0.20.0' + REVISION : '1.0.1' }; var assign = require('object-assign'); @@ -844,11 +1494,11 @@ assign(ROSLIB, require('./urdf')); module.exports = ROSLIB; -},{"./actionlib":9,"./core":18,"./math":23,"./tf":26,"./urdf":38,"object-assign":2}],4:[function(require,module,exports){ +},{"./actionlib":11,"./core":20,"./math":25,"./tf":28,"./urdf":40,"object-assign":3}],6:[function(require,module,exports){ (function (global){ global.ROSLIB = require('./RosLib'); }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"./RosLib":3}],5:[function(require,module,exports){ +},{"./RosLib":5}],7:[function(require,module,exports){ /** * @fileOverview * @author Russell Toris - rctoris@wpi.edu @@ -993,7 +1643,7 @@ ActionClient.prototype.dispose = function() { module.exports = ActionClient; -},{"../core/Message":10,"../core/Topic":17,"eventemitter2":1}],6:[function(require,module,exports){ +},{"../core/Message":12,"../core/Topic":19,"eventemitter2":2}],8:[function(require,module,exports){ /** * @fileOverview * @author Justin Young - justin@oodar.com.au @@ -1082,7 +1732,7 @@ ActionListener.prototype.__proto__ = EventEmitter2.prototype; module.exports = ActionListener; -},{"../core/Message":10,"../core/Topic":17,"eventemitter2":1}],7:[function(require,module,exports){ +},{"../core/Message":12,"../core/Topic":19,"eventemitter2":2}],9:[function(require,module,exports){ /** * @fileOverview * @author Russell Toris - rctoris@wpi.edu @@ -1172,7 +1822,7 @@ Goal.prototype.cancel = function() { }; module.exports = Goal; -},{"../core/Message":10,"eventemitter2":1}],8:[function(require,module,exports){ +},{"../core/Message":12,"eventemitter2":2}],10:[function(require,module,exports){ /** * @fileOverview * @author Laura Lindzey - lindzey@gmail.com @@ -1381,7 +2031,7 @@ SimpleActionServer.prototype.setPreempted = function() { }; module.exports = SimpleActionServer; -},{"../core/Message":10,"../core/Topic":17,"eventemitter2":1}],9:[function(require,module,exports){ +},{"../core/Message":12,"../core/Topic":19,"eventemitter2":2}],11:[function(require,module,exports){ var Ros = require('../core/Ros'); var mixin = require('../mixin'); @@ -1394,7 +2044,7 @@ var action = module.exports = { mixin(Ros, ['ActionClient', 'SimpleActionServer'], action); -},{"../core/Ros":12,"../mixin":24,"./ActionClient":5,"./ActionListener":6,"./Goal":7,"./SimpleActionServer":8}],10:[function(require,module,exports){ +},{"../core/Ros":14,"../mixin":26,"./ActionClient":7,"./ActionListener":8,"./Goal":9,"./SimpleActionServer":10}],12:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - baalexander@gmail.com @@ -1413,7 +2063,7 @@ function Message(values) { } module.exports = Message; -},{"object-assign":2}],11:[function(require,module,exports){ +},{"object-assign":3}],13:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - baalexander@gmail.com @@ -1497,7 +2147,7 @@ Param.prototype.delete = function(callback) { }; module.exports = Param; -},{"./Service":13,"./ServiceRequest":14}],12:[function(require,module,exports){ +},{"./Service":15,"./ServiceRequest":16}],14:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - baalexander@gmail.com @@ -1569,8 +2219,12 @@ Ros.prototype.connect = function(url) { this.socket.on('error', this.socket.onerror); } else if (this.transportLibrary.constructor.name === 'RTCPeerConnection') { this.socket = assign(this.transportLibrary.createDataChannel(url, this.transportOptions), socketAdapter(this)); - }else { - this.socket = assign(new WebSocket(url), socketAdapter(this)); + } else { + if (!this.socket || this.socket.readyState === WebSocket.CLOSED) { + var sock = new WebSocket(url); + sock.binaryType = 'arraybuffer'; + this.socket = assign(sock, socketAdapter(this)); + } } }; @@ -2118,7 +2772,7 @@ Ros.prototype.decodeTypeDefs = function(defs) { module.exports = Ros; -},{"./Service":13,"./ServiceRequest":14,"./SocketAdapter.js":16,"eventemitter2":1,"object-assign":2,"ws":39}],13:[function(require,module,exports){ +},{"./Service":15,"./ServiceRequest":16,"./SocketAdapter.js":18,"eventemitter2":2,"object-assign":3,"ws":42}],15:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - baalexander@gmail.com @@ -2148,7 +2802,8 @@ function Service(options) { } Service.prototype.__proto__ = EventEmitter2.prototype; /** - * Calls the service. Returns the service response in the callback. + * Calls the service. Returns the service response in the + * callback. Does nothing if this service is currently advertised. * * @param request - the ROSLIB.ServiceRequest to send * @param callback - function with params: @@ -2179,17 +2834,22 @@ Service.prototype.callService = function(request, callback, failedCallback) { op : 'call_service', id : serviceCallId, service : this.name, + type: this.serviceType, args : request }; this.ros.callOnConnection(call); }; /** - * Every time a message is published for the given topic, the callback - * will be called with the message object. + * Advertise the service. This turns the Service object from a client + * into a server. The callback will be called with every request + * that's made on this service. * - * @param callback - function with the following params: - * * message - the published message + * @param callback - This works similarly to the callback for a C++ service and should take the following params: + * * request - the service request + * * response - an empty dictionary. Take care not to overwrite this. Instead, only modify the values within. + * It should return true if the service has finished successfully, + * i.e. without any fatal errors. */ Service.prototype.advertise = function(callback) { if (this.isAdvertised || typeof callback !== 'function') { @@ -2236,7 +2896,8 @@ Service.prototype._serviceResponse = function(rosbridgeRequest) { }; module.exports = Service; -},{"./ServiceRequest":14,"./ServiceResponse":15,"eventemitter2":1}],14:[function(require,module,exports){ + +},{"./ServiceRequest":16,"./ServiceResponse":17,"eventemitter2":2}],16:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - balexander@willowgarage.com @@ -2255,7 +2916,7 @@ function ServiceRequest(values) { } module.exports = ServiceRequest; -},{"object-assign":2}],15:[function(require,module,exports){ +},{"object-assign":3}],17:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - balexander@willowgarage.com @@ -2274,7 +2935,7 @@ function ServiceResponse(values) { } module.exports = ServiceResponse; -},{"object-assign":2}],16:[function(require,module,exports){ +},{"object-assign":3}],18:[function(require,module,exports){ /** * Socket event handling utilities for handling events on either * WebSocket and TCP sockets @@ -2286,6 +2947,8 @@ module.exports = ServiceResponse; 'use strict'; var decompressPng = require('../util/decompressPng'); +var CBOR = require('cbor-js'); +var typedArrayTagger = require('../util/cborTypedArrayTags'); var WebSocket = require('ws'); var BSON = null; if(typeof bson !== 'undefined'){ @@ -2383,6 +3046,9 @@ function SocketAdapter(client) { decodeBSON(data.data, function (message) { handlePng(message, handleMessage); }); + } else if (data.data instanceof ArrayBuffer) { + var decoded = CBOR.decode(data.data, typedArrayTagger); + handleMessage(decoded); } else { var message = JSON.parse(typeof data === 'string' ? data : data.data); handlePng(message, handleMessage); @@ -2393,7 +3059,7 @@ function SocketAdapter(client) { module.exports = SocketAdapter; -},{"../util/decompressPng":41,"ws":39}],17:[function(require,module,exports){ +},{"../util/cborTypedArrayTags":41,"../util/decompressPng":44,"cbor-js":1,"ws":42}],19:[function(require,module,exports){ /** * @fileoverview * @author Brandon Alexander - baalexander@gmail.com @@ -2414,7 +3080,7 @@ var Message = require('./Message'); * * ros - the ROSLIB.Ros connection handle * * name - the topic name, like /cmd_vel * * messageType - the message type, like 'std_msgs/String' - * * compression - the type of compression to use, like 'png' + * * compression - the type of compression to use, like 'png' or 'cbor' * * throttle_rate - the rate (in ms in between messages) at which to throttle the topics * * queue_size - the queue created at bridge side for re-publishing webtopics (defaults to 100) * * latch - latch the topic when publishing @@ -2436,9 +3102,10 @@ function Topic(options) { // Check for valid compression types if (this.compression && this.compression !== 'png' && - this.compression !== 'none') { + this.compression !== 'cbor' && this.compression !== 'none') { this.emit('warning', this.compression + ' compression is not supported. No compression will be used.'); + this.compression = 'none'; } // Check if throttle rate is negative @@ -2601,7 +3268,7 @@ Topic.prototype.publish = function(message) { module.exports = Topic; -},{"./Message":10,"eventemitter2":1}],18:[function(require,module,exports){ +},{"./Message":12,"eventemitter2":2}],20:[function(require,module,exports){ var mixin = require('../mixin'); var core = module.exports = { @@ -2616,7 +3283,7 @@ var core = module.exports = { mixin(core.Ros, ['Param', 'Service', 'Topic'], core); -},{"../mixin":24,"./Message":10,"./Param":11,"./Ros":12,"./Service":13,"./ServiceRequest":14,"./ServiceResponse":15,"./Topic":17}],19:[function(require,module,exports){ +},{"../mixin":26,"./Message":12,"./Param":13,"./Ros":14,"./Service":15,"./ServiceRequest":16,"./ServiceResponse":17,"./Topic":19}],21:[function(require,module,exports){ /** * @fileoverview * @author David Gossow - dgossow@willowgarage.com @@ -2662,8 +3329,34 @@ Pose.prototype.clone = function() { return new Pose(this); }; +/** + * Multiplies this pose with another pose without altering this pose. + * + * @returns Result of multiplication. + */ +Pose.prototype.multiply = function(pose) { + var p = pose.clone(); + p.applyTransform({ rotation: this.orientation, translation: this.position }); + return p; +}; + +/** + * Computes the inverse of this pose. + * + * @returns Inverse of pose. + */ +Pose.prototype.getInverse = function() { + var inverse = this.clone(); + inverse.orientation.invert(); + inverse.position.multiplyQuaternion(inverse.orientation); + inverse.position.x *= -1; + inverse.position.y *= -1; + inverse.position.z *= -1; + return inverse; +}; + module.exports = Pose; -},{"./Quaternion":20,"./Vector3":22}],20:[function(require,module,exports){ +},{"./Quaternion":22,"./Vector3":24}],22:[function(require,module,exports){ /** * @fileoverview * @author David Gossow - dgossow@willowgarage.com @@ -2757,7 +3450,7 @@ Quaternion.prototype.clone = function() { module.exports = Quaternion; -},{}],21:[function(require,module,exports){ +},{}],23:[function(require,module,exports){ /** * @fileoverview * @author David Gossow - dgossow@willowgarage.com @@ -2791,7 +3484,7 @@ Transform.prototype.clone = function() { }; module.exports = Transform; -},{"./Quaternion":20,"./Vector3":22}],22:[function(require,module,exports){ +},{"./Quaternion":22,"./Vector3":24}],24:[function(require,module,exports){ /** * @fileoverview * @author David Gossow - dgossow@willowgarage.com @@ -2860,7 +3553,7 @@ Vector3.prototype.clone = function() { }; module.exports = Vector3; -},{}],23:[function(require,module,exports){ +},{}],25:[function(require,module,exports){ module.exports = { Pose: require('./Pose'), Quaternion: require('./Quaternion'), @@ -2868,7 +3561,7 @@ module.exports = { Vector3: require('./Vector3') }; -},{"./Pose":19,"./Quaternion":20,"./Transform":21,"./Vector3":22}],24:[function(require,module,exports){ +},{"./Pose":21,"./Quaternion":22,"./Transform":23,"./Vector3":24}],26:[function(require,module,exports){ /** * Mixin a feature to the core/Ros prototype. * For example, mixin(Ros, ['Topic'], {Topic: }) @@ -2887,7 +3580,7 @@ module.exports = function(Ros, classes, features) { }); }; -},{}],25:[function(require,module,exports){ +},{}],27:[function(require,module,exports){ /** * @fileoverview * @author David Gossow - dgossow@willowgarage.com @@ -3108,7 +3801,7 @@ TFClient.prototype.dispose = function() { module.exports = TFClient; -},{"../actionlib/ActionClient":5,"../actionlib/Goal":7,"../core/Service.js":13,"../core/ServiceRequest.js":14,"../math/Transform":21}],26:[function(require,module,exports){ +},{"../actionlib/ActionClient":7,"../actionlib/Goal":9,"../core/Service.js":15,"../core/ServiceRequest.js":16,"../math/Transform":23}],28:[function(require,module,exports){ var Ros = require('../core/Ros'); var mixin = require('../mixin'); @@ -3117,7 +3810,7 @@ var tf = module.exports = { }; mixin(Ros, ['TFClient'], tf); -},{"../core/Ros":12,"../mixin":24,"./TFClient":25}],27:[function(require,module,exports){ +},{"../core/Ros":14,"../mixin":26,"./TFClient":27}],29:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com @@ -3148,7 +3841,7 @@ function UrdfBox(options) { } module.exports = UrdfBox; -},{"../math/Vector3":22,"./UrdfTypes":36}],28:[function(require,module,exports){ +},{"../math/Vector3":24,"./UrdfTypes":38}],30:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com @@ -3172,7 +3865,7 @@ function UrdfColor(options) { } module.exports = UrdfColor; -},{}],29:[function(require,module,exports){ +},{}],31:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com @@ -3195,12 +3888,16 @@ function UrdfCylinder(options) { } module.exports = UrdfCylinder; -},{"./UrdfTypes":36}],30:[function(require,module,exports){ +},{"./UrdfTypes":38}],32:[function(require,module,exports){ /** * @fileOverview * @author David V. Lu!! davidvlu@gmail.com */ +var Pose = require('../math/Pose'); +var Vector3 = require('../math/Vector3'); +var Quaternion = require('../math/Quaternion'); + /** * A Joint element in a URDF. * @@ -3227,11 +3924,64 @@ function UrdfJoint(options) { this.minval = parseFloat( limits[0].getAttribute('lower') ); this.maxval = parseFloat( limits[0].getAttribute('upper') ); } + + // Origin + var origins = options.xml.getElementsByTagName('origin'); + if (origins.length === 0) { + // use the identity as the default + this.origin = new Pose(); + } else { + // Check the XYZ + var xyz = origins[0].getAttribute('xyz'); + var position = new Vector3(); + if (xyz) { + xyz = xyz.split(' '); + position = new Vector3({ + x : parseFloat(xyz[0]), + y : parseFloat(xyz[1]), + z : parseFloat(xyz[2]) + }); + } + + // Check the RPY + var rpy = origins[0].getAttribute('rpy'); + var orientation = new Quaternion(); + if (rpy) { + rpy = rpy.split(' '); + // Convert from RPY + var roll = parseFloat(rpy[0]); + var pitch = parseFloat(rpy[1]); + var yaw = parseFloat(rpy[2]); + var phi = roll / 2.0; + var the = pitch / 2.0; + var psi = yaw / 2.0; + var x = Math.sin(phi) * Math.cos(the) * Math.cos(psi) - Math.cos(phi) * Math.sin(the) + * Math.sin(psi); + var y = Math.cos(phi) * Math.sin(the) * Math.cos(psi) + Math.sin(phi) * Math.cos(the) + * Math.sin(psi); + var z = Math.cos(phi) * Math.cos(the) * Math.sin(psi) - Math.sin(phi) * Math.sin(the) + * Math.cos(psi); + var w = Math.cos(phi) * Math.cos(the) * Math.cos(psi) + Math.sin(phi) * Math.sin(the) + * Math.sin(psi); + + orientation = new Quaternion({ + x : x, + y : y, + z : z, + w : w + }); + orientation.normalize(); + } + this.origin = new Pose({ + position : position, + orientation : orientation + }); + } } module.exports = UrdfJoint; -},{}],31:[function(require,module,exports){ +},{"../math/Pose":21,"../math/Quaternion":22,"../math/Vector3":24}],33:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com @@ -3260,7 +4010,7 @@ function UrdfLink(options) { } module.exports = UrdfLink; -},{"./UrdfVisual":37}],32:[function(require,module,exports){ +},{"./UrdfVisual":39}],34:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com @@ -3310,7 +4060,7 @@ UrdfMaterial.prototype.assign = function(obj) { module.exports = UrdfMaterial; -},{"./UrdfColor":28,"object-assign":2}],33:[function(require,module,exports){ +},{"./UrdfColor":30,"object-assign":3}],35:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com @@ -3347,7 +4097,7 @@ function UrdfMesh(options) { } module.exports = UrdfMesh; -},{"../math/Vector3":22,"./UrdfTypes":36}],34:[function(require,module,exports){ +},{"../math/Vector3":24,"./UrdfTypes":38}],36:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com @@ -3444,7 +4194,7 @@ function UrdfModel(options) { module.exports = UrdfModel; -},{"./UrdfJoint":30,"./UrdfLink":31,"./UrdfMaterial":32,"xmldom":42}],35:[function(require,module,exports){ +},{"./UrdfJoint":32,"./UrdfLink":33,"./UrdfMaterial":34,"xmldom":45}],37:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com @@ -3466,7 +4216,7 @@ function UrdfSphere(options) { } module.exports = UrdfSphere; -},{"./UrdfTypes":36}],36:[function(require,module,exports){ +},{"./UrdfTypes":38}],38:[function(require,module,exports){ module.exports = { URDF_SPHERE : 0, URDF_BOX : 1, @@ -3474,7 +4224,7 @@ module.exports = { URDF_MESH : 3 }; -},{}],37:[function(require,module,exports){ +},{}],39:[function(require,module,exports){ /** * @fileOverview * @author Benjamin Pitzer - ben.pitzer@gmail.com @@ -3603,7 +4353,7 @@ function UrdfVisual(options) { } module.exports = UrdfVisual; -},{"../math/Pose":19,"../math/Quaternion":20,"../math/Vector3":22,"./UrdfBox":27,"./UrdfCylinder":29,"./UrdfMaterial":32,"./UrdfMesh":33,"./UrdfSphere":35}],38:[function(require,module,exports){ +},{"../math/Pose":21,"../math/Quaternion":22,"../math/Vector3":24,"./UrdfBox":29,"./UrdfCylinder":31,"./UrdfMaterial":34,"./UrdfMesh":35,"./UrdfSphere":37}],40:[function(require,module,exports){ module.exports = require('object-assign')({ UrdfBox: require('./UrdfBox'), UrdfColor: require('./UrdfColor'), @@ -3616,17 +4366,135 @@ module.exports = require('object-assign')({ UrdfVisual: require('./UrdfVisual') }, require('./UrdfTypes')); -},{"./UrdfBox":27,"./UrdfColor":28,"./UrdfCylinder":29,"./UrdfLink":31,"./UrdfMaterial":32,"./UrdfMesh":33,"./UrdfModel":34,"./UrdfSphere":35,"./UrdfTypes":36,"./UrdfVisual":37,"object-assign":2}],39:[function(require,module,exports){ -(function (global){ -module.exports = global.WebSocket; -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}],40:[function(require,module,exports){ +},{"./UrdfBox":29,"./UrdfColor":30,"./UrdfCylinder":31,"./UrdfLink":33,"./UrdfMaterial":34,"./UrdfMesh":35,"./UrdfModel":36,"./UrdfSphere":37,"./UrdfTypes":38,"./UrdfVisual":39,"object-assign":3}],41:[function(require,module,exports){ +'use strict'; + +var UPPER32 = Math.pow(2, 32); + +var warnedPrecision = false; +function warnPrecision() { + if (!warnedPrecision) { + warnedPrecision = true; + console.warn('CBOR 64-bit integer array values may lose precision. No further warnings.'); + } +} + +/** + * Unpacks 64-bit unsigned integer from byte array. + * @param {Uint8Array} bytes +*/ +function decodeUint64LE(bytes) { + warnPrecision(); + + var byteLen = bytes.byteLength; + var offset = bytes.byteOffset; + var arrLen = byteLen / 8; + + var buffer = bytes.buffer.slice(offset, offset + byteLen); + var uint32View = new Uint32Array(buffer); + + var arr = new Array(arrLen); + for (var i = 0; i < arrLen; i++) { + var si = i * 2; + var lo = uint32View[si]; + var hi = uint32View[si+1]; + arr[i] = lo + UPPER32 * hi; + } + + return arr; +} + +/** + * Unpacks 64-bit signed integer from byte array. + * @param {Uint8Array} bytes +*/ +function decodeInt64LE(bytes) { + warnPrecision(); + + var byteLen = bytes.byteLength; + var offset = bytes.byteOffset; + var arrLen = byteLen / 8; + + var buffer = bytes.buffer.slice(offset, offset + byteLen); + var uint32View = new Uint32Array(buffer); + var int32View = new Int32Array(buffer); + + var arr = new Array(arrLen); + for (var i = 0; i < arrLen; i++) { + var si = i * 2; + var lo = uint32View[si]; + var hi = int32View[si+1]; + arr[i] = lo + UPPER32 * hi; + } + + return arr; +} + +/** + * Unpacks typed array from byte array. + * @param {Uint8Array} bytes + * @param {type} ArrayType - desired output array type +*/ +function decodeNativeArray(bytes, ArrayType) { + var byteLen = bytes.byteLength; + var offset = bytes.byteOffset; + var buffer = bytes.buffer.slice(offset, offset + byteLen); + return new ArrayType(buffer); +} + +/** + * Support a subset of draft CBOR typed array tags: + * + * Only support little-endian tags for now. + */ +var nativeArrayTypes = { + 64: Uint8Array, + 69: Uint16Array, + 70: Uint32Array, + 72: Int8Array, + 77: Int16Array, + 78: Int32Array, + 85: Float32Array, + 86: Float64Array +}; + +/** + * We can also decode 64-bit integer arrays, since ROS has these types. + */ +var conversionArrayTypes = { + 71: decodeUint64LE, + 79: decodeInt64LE +}; + +/** + * Handles CBOR typed array tags during decoding. + * @param {Uint8Array} data + * @param {Number} tag + */ +function cborTypedArrayTagger(data, tag) { + if (tag in nativeArrayTypes) { + var arrayType = nativeArrayTypes[tag]; + return decodeNativeArray(data, arrayType); + } + if (tag in conversionArrayTypes) { + return conversionArrayTypes[tag](data); + } + return data; +} + +if (typeof module !== 'undefined' && module.exports) { + module.exports = cborTypedArrayTagger; +} + +},{}],42:[function(require,module,exports){ +module.exports = window.WebSocket; + +},{}],43:[function(require,module,exports){ /* global document */ module.exports = function Canvas() { return document.createElement('canvas'); }; -},{}],41:[function(require,module,exports){ -(function (global){ +},{}],44:[function(require,module,exports){ /** * @fileOverview * @author Graeme Yeates - github.com/megawac @@ -3635,7 +4503,7 @@ module.exports = function Canvas() { 'use strict'; var Canvas = require('canvas'); -var Image = Canvas.Image || global.Image; +var Image = Canvas.Image || window.Image; /** * If a message was compressed as a PNG image (a compression hack since @@ -3683,11 +4551,10 @@ function decompressPng(data, callback) { } module.exports = decompressPng; -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{"canvas":40}],42:[function(require,module,exports){ -(function (global){ -exports.DOMImplementation = global.DOMImplementation; -exports.XMLSerializer = global.XMLSerializer; -exports.DOMParser = global.DOMParser; -}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) -},{}]},{},[4]); + +},{"canvas":43}],45:[function(require,module,exports){ +exports.DOMImplementation = window.DOMImplementation; +exports.XMLSerializer = window.XMLSerializer; +exports.DOMParser = window.DOMParser; + +},{}]},{},[6]); From 7cc91b2e32aa9b8c51fc20c163f8c9d9d5d531b1 Mon Sep 17 00:00:00 2001 From: Alexey Rogachevskiy Date: Mon, 17 Feb 2020 21:54:20 +0300 Subject: [PATCH 07/14] Install ptvsd by default (#217) * builder: Install ptvsd by default * builder: Add ptvsd version check --- builder/image-software.sh | 4 ++++ builder/test/tests.sh | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/builder/image-software.sh b/builder/image-software.sh index bb8fec54..a94fbf18 100755 --- a/builder/image-software.sh +++ b/builder/image-software.sh @@ -153,6 +153,10 @@ cp -R node-v10.15.0-linux-armv6l/* /usr/local/ rm -rf node-v10.15.0-linux-armv6l/ rm node-v10.15.0-linux-armv6l.tar.gz +echo_stamp "Installing ptvsd" +my_travis_retry pip install ptvsd +my_travis_retry pip3 install ptvsd + echo_stamp "Add .vimrc" cat << EOF > /home/pi/.vimrc set mouse-=a diff --git a/builder/test/tests.sh b/builder/test/tests.sh index ef3c39b1..a8dcd43f 100755 --- a/builder/test/tests.sh +++ b/builder/test/tests.sh @@ -12,6 +12,10 @@ python3 --version ipython --version ipython3 --version +# ptvsd does not have a stand-alone binary +python -m ptvsd --version +python3 -m ptvsd --version + node -v npm -v From 22ba3a1406b89a34fd7fdc7c66f1d9c637382701 Mon Sep 17 00:00:00 2001 From: Alexey Rogachevskiy Date: Wed, 19 Feb 2020 12:43:32 +0300 Subject: [PATCH 08/14] builder: Use raspbian 2020-02-13 release as base --- builder/image-build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/image-build.sh b/builder/image-build.sh index 0bd9dab1..f270d75d 100755 --- a/builder/image-build.sh +++ b/builder/image-build.sh @@ -15,7 +15,7 @@ set -e # Exit immidiately on non-zero result -SOURCE_IMAGE="https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-09-30/2019-09-26-raspbian-buster-lite.zip" +SOURCE_IMAGE="https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2020-02-14/2020-02-13-raspbian-buster-lite.zip" export DEBIAN_FRONTEND=${DEBIAN_FRONTEND:='noninteractive'} export LANG=${LANG:='C.UTF-8'} From 2672b6784fdb967b5d6d586545b0eb242857e8c5 Mon Sep 17 00:00:00 2001 From: Alexey Rogachevskiy Date: Wed, 19 Feb 2020 15:17:53 +0300 Subject: [PATCH 09/14] builder: Set country for wpa_supplicant --- builder/image-network.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/builder/image-network.sh b/builder/image-network.sh index 9f50b147..db0a165a 100755 --- a/builder/image-network.sh +++ b/builder/image-network.sh @@ -41,7 +41,12 @@ interface wlan0 static ip_address=192.168.11.1/24 EOF -echo_stamp "#2 Write dhcp-config to /etc/dnsmasq.conf" +echo_stamp "#2 Set wpa_supplicant country" +cat << EOF >> /etc/wpa_supplicant/wpa_supplicant.conf +country=GB +EOF + +echo_stamp "#3 Write dhcp-config to /etc/dnsmasq.conf" cat << EOF >> /etc/dnsmasq.conf interface=wlan0 @@ -54,4 +59,4 @@ domain-needed quiet-dhcp6 EOF -echo_stamp "#3 End of network installation" +echo_stamp "#4 End of network installation" From 4c940f0b8b04fbd62c15621d9440f347932d90bd Mon Sep 17 00:00:00 2001 From: Alexey Rogachevskiy Date: Wed, 19 Feb 2020 17:00:49 +0300 Subject: [PATCH 10/14] init_rpi: Unblock wi-fi on first boot --- builder/assets/init_rpi.sh | 3 +++ builder/image-network.sh | 1 + 2 files changed, 4 insertions(+) diff --git a/builder/assets/init_rpi.sh b/builder/assets/init_rpi.sh index a794f7e1..606813e6 100755 --- a/builder/assets/init_rpi.sh +++ b/builder/assets/init_rpi.sh @@ -51,6 +51,9 @@ network={ } EOF +echo_stamp "Unblocking wireless interface" +rfkill unblock wifi + NEW_HOSTNAME=$(echo ${NEW_SSID} | tr '[:upper:]' '[:lower:]') echo_stamp "Setting hostname to $NEW_HOSTNAME" hostnamectl set-hostname $NEW_HOSTNAME diff --git a/builder/image-network.sh b/builder/image-network.sh index db0a165a..7394b250 100755 --- a/builder/image-network.sh +++ b/builder/image-network.sh @@ -42,6 +42,7 @@ static ip_address=192.168.11.1/24 EOF echo_stamp "#2 Set wpa_supplicant country" + cat << EOF >> /etc/wpa_supplicant/wpa_supplicant.conf country=GB EOF From e05431cc754c0b415f6c4996756e050a97a1139e Mon Sep 17 00:00:00 2001 From: Oleg Kalachev Date: Wed, 19 Feb 2020 18:27:09 +0300 Subject: [PATCH 11/14] docs: fix sonar example --- docs/en/sonar.md | 4 ++-- docs/ru/sonar.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/sonar.md b/docs/en/sonar.md index 3a11ac92..176768c5 100644 --- a/docs/en/sonar.md +++ b/docs/en/sonar.md @@ -73,8 +73,8 @@ def read_distance(): global low done.clear() pi.gpio_trigger(TRIG, 50, 1) - done.wait(timeout=5) - return low / 58.0 / 100.0 + if done.wait(timeout=5): + return low / 58.0 / 100.0 pi.set_mode(TRIG, pigpio.OUTPUT) pi.set_mode(ECHO, pigpio.INPUT) diff --git a/docs/ru/sonar.md b/docs/ru/sonar.md index d98d79f0..042530f5 100644 --- a/docs/ru/sonar.md +++ b/docs/ru/sonar.md @@ -73,8 +73,8 @@ def read_distance(): global low done.clear() pi.gpio_trigger(TRIG, 50, 1) - done.wait(timeout=5) - return low / 58.0 / 100.0 + if done.wait(timeout=5): + return low / 58.0 / 100.0 pi.set_mode(TRIG, pigpio.OUTPUT) pi.set_mode(ECHO, pigpio.INPUT) From 6fbdfb7817078ef3ad0443f9a9f6d699d23f9ac9 Mon Sep 17 00:00:00 2001 From: Oleg Kalachev Date: Wed, 19 Feb 2020 18:28:14 +0300 Subject: [PATCH 12/14] clover.launch: enable optical flow by default --- clover/launch/clover.launch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clover/launch/clover.launch b/clover/launch/clover.launch index 02680f57..50646e66 100644 --- a/clover/launch/clover.launch +++ b/clover/launch/clover.launch @@ -5,7 +5,7 @@ - + From d01e6990b1da98509711e63a913a1d68f0c842d5 Mon Sep 17 00:00:00 2001 From: Oleg Kalachev Date: Thu, 20 Feb 2020 00:21:44 +0300 Subject: [PATCH 13/14] docs: add failsafe article --- docs/assets/qgc-failsafe.png | Bin 0 -> 616313 bytes docs/ru/SUMMARY.md | 1 + docs/ru/failsafe.md | 13 +++++++++++++ docs/ru/power.md | 2 ++ 4 files changed, 16 insertions(+) create mode 100644 docs/assets/qgc-failsafe.png create mode 100644 docs/ru/failsafe.md diff --git a/docs/assets/qgc-failsafe.png b/docs/assets/qgc-failsafe.png new file mode 100644 index 0000000000000000000000000000000000000000..610ad59d644f56ce042e8810797a3de66183e434 GIT binary patch literal 616313 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

YTn?^{HI?b-HxAE5~iWH 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

)-gA8{*%O{F#hOk8vB?AkVi>Ee3 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%0D30bIE&wOJ(qn`w~}W`(iD%JQ`A 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

5;0NWme}Xwcrq7#j$xH7yX#f$hV7(E>5Id zIEYg`h|hThn*OyKrDn91g2&ngRrh+YxrnBfCQ&s+X)N%}cng{DHu0Hm$1Yl8IsS-g zJ{b#bC_VrY&PThgTK>TCje|B0{}cr}F{#>Z#%M9cj)lJ`wedncc;!1ADbu5eeB8~&rMY>cA zFZMl_rPjRhU+Ic29X$(6v^1+h-GS9Y4*Ck-va^;Q7ZqI z3oPE<+dlagT}J8h3scG;>D>WgW=mg2BN8b1>?a)fr1=iH zzW(3Pd7BEevS@ zJwzOaQ&knUW!i)VxtfA(?t#m}r-6gNO3)5%nF_P4g0CHt?DwmBktq zly`uw+iepW=-Q(^#2}WZF_}ZSR?a7Wl8_ki_~aPN-Wr~h-@UI&gA|2u9Zx5v$<FN#UGZFL&2M;=v|uEc??RR;|{>Z&#td z(H;L#@an_*NiESPZrPS++*JaGVJFt3HUv;#LAk6go4~0*G77W_{r^M`*vXARxP?2-L1`d%@#D6vRx?i8XS^9Uxuz?jlB#{dLkc%J!34j3+cy`kE`Uet zxfMTv`)J-ISogbiAQnNDJR2oRg^R0c<}W_j3TrSm(OoKnu!$}^Z_6YzCQ?G{IXVwt zHS_)clz%8C!;-lt04I2j@xRkj@s@zWIPEF_F$&JWJN;wqGl2Yy^0ODeaT4q?4MIS) zy_%k3ve4f>&_~w!)u!T7CkDE_$<>;Ok3~|P=d34P(Jx@EjN6vMUV0nP`=mv+Vf5!N zYi0=ToL|U7y5U^cx&)Fz?6sr4lRR-~#jN0Dn7!-n3D;$WVMW4aqHNCUxYZsR=R%=7=h{85yC^8XiM)Q=jes@H&Kj(HR5sB3AFV2aX^Os?#J-kn2_} zpmTEtBJf{9dB|9 z0od$UB}@9aptGak>xV%FsEjxoj1-Rq@llR*Pg3*hbSn7fh2P6;)+$ zkoWvwQ2T+aJ?lF8VWZkYExq8XoF`aBE&FdqrgXRY+xjDygxysg%ZgS5+MprG70 zM^EDF&X!#?Budp}9ERYiZfr|d6~o?@U{@Iu`ip0!S@YC%o#+`S4~l&>Qg!%DzUOPG zB|*=#q~aG6z&3G-0iv;16KF{uyV{y|BYzE~cZTknv%<^)k#0N44%wdpjXPQoHs1+z z-++G=MlS36o`2<4Z4kDGFogRnsOOslBAD1eC=CB^C`=G|Gxaa6DQ}@Lk|5v5m3bbO zDT44n&f3`csHv&^W8~$BFD|jR&D)R94(XmAo~pc+0}TOnmWNl2sw6AxsmPMLHeD0r z_9i{nWnMBdPU}QE!;{n1PhJ=OAB}@v=d8`^&Q{K2gotbgs_cpreIthb9fo)I7#kKT z3x$AojD7JSzr*DhyGqtwZ=~!OCrkuhe5rt#b>NNVylrA?bG)Fp6YXUAZa^?nNCP{^ z*lpfwgpbPyt{-|YW=YbpOry-FFPFHe9tW^YSB9qPeMe~>PloN!KS-SSupwK8d11+J z587UZN4I&G;iSdfiM9>=f5tzGUt!PB6x2w4=5w&HN@#xoX^ik+3tfX8SS0pMho54f zw)D7t*)Q?~8`1fI-yU}5umNXg{;T{g>P2#i`OFkn#a8yYqost&WNslzFFb{e+~WR%Q>csRM0PlWSnhs7P*}# z#-M0PU>S?6DNjpWG;3Lr$whx@{G2tQCnz^%hOs+t_ku31YUM|#I6@yB>5~@l`(TM& zHaA%$i=ZZ?UbH3nn}!XGMBoSu2PWie?=T^)Wqs$ez&*rjg=m4c4tALP)2J}>t^wKj zQ@U=V6*jJ$(3S9zi{;@)vo!-n{Zf_J=VO4{A1bm8-#X$-HOMoRA;}Eo#PqZ~-JP*Y zmYl$ws$MdyzJ;&@mRr8tU=66W{xF@Pf3T-_s}-8nzj0Cu3Oo#+V!gi7Fdu3(lSlvA z-kD=&#{4W5J+N3IEbTYytdAcHq#6|TQ2-0AwzaiHSdg@qg*nzN0;9?b4+9x&Wr_&R zX;&x{Sm;aXQ=KmPmFwh%?I#I54~W|t*X6@=l!lfpT#dJhx!n|YC26+2Sk?Lrtj{F( z=gYdl2EmbxBtEvkCygQFHPuuZU;G(XD9eZ}iRzEARzBn>V=M!Q2mxh7ol%!WWT(Y6 z^wi>)bjfd=LWhf-jvX?7>!)fTI(b??5@PTNQH+~@c&>B`DmWZzofpq=Ji;={q>>JBB2oI4&8haCa!(5{OON1%cst6Y1XcR zie-ynJ32c=-ocMc^6Ts#=J zTBmziN$15{iSIVC3^zXq!`i9|Dc0h~YP`?aYChVf)q3s1_YHK-ndCGM-v(Bn{QZAL zOetB=)lt~V6zOsACre7cTfPbT%DU!wlZ_JzAs8p42-O%I9JhI6{}g9PLkon5!JUqC zOB@ck1g<7F^holCpYh#n2C;bSUz74{%*LetrGRb}E=6^d2 z+{`0`+ikr5!N|$UMKiWOR@`(raPuZC$y)mT=(keCT!*&0215b&YS>@u>Ya!Dq=`Ru z6~0A0Ag;L4<$mokATHj|`Sw1VBYm`sA#KY0tj2xiBBl!BhhRB(D-wc&TrRqYKeFXzz8aJN@{WDq^PgFi)Wy{A`*t||BnvlZ!Lyq3F=}M6C zCK=S&fpM^bx(2H<$=xG$FKn%eo+@UkAn&XG#rAXa^^ZKzwmTk05bExv}NY|WyYBt15LEtE$WE?AmZ zvRw>@u?D;@L%&X3K>sQE7XQQ7CZSRGjbL*?FAI0@XvZEy0JjBAlFm7Vxw0awG+*%D z3~8gGtz9$$qQdXOaSQG1k?w|Nu_W$JK=ZKf5x*M98=YZfzdl;QR=NJo?lsAd2ylcV z^%@L@rASO-{Fp%trMD#)&{+PXwHj!(Tkrgnu8aZUFS*G5*LAp3plmrt56hKTQowPQ zn9y`1}hnPYVt8Tle1%PlFlEvfGv6ZR8Y(}LWr zJun!|mTjlE;)(4Kd^3PtpJeflv`+xqu>80mF$UsjM5wVOm_x+l zjuK4P3`FRHwq-{Kzde|kCq1@+LpS5VH@n#wh23xz7e!Y*_y)-&z8PT=3_bru-ysiA z(7Jx@WnAeOGw~IiItxWmYZ0lAw*Ki4yywT}M7#Tt8hB1BBNhAVoj#1q@!!GJ7j9DvHOtHK6%&d`2;)#5VX9AW z0Z`xarAw`Ww=#--#ruIijQX~L4OGf}O7a}NH{&@uXU}NWt%&&npwPc?4E2UXJ_a5t zOpt>To@uzn-B|BVg>gKVpp!3Xf668)aYghQH%Yj^WC$W)vNa z8^RX0q|Z+~u$_|mp=^!M#>~?=F{{KRNVqy?LS~^N61oO9*HDAC8wohSX@YOX}*1A}3goxlJ0uQQ+c zA*YXMSN)@nJ_9f`E$uLmoYspqqh2ecEdyHE9ojMCF|{Bu_H^V^#wyT41MImTRhCpp z?Y)?Z@3tIVFBuN09m$~!H;-FfNS27wtu{IyG1ayo66S`{7sm75(5V^Mn|h(D9b(~p zuGr~(N=e0mQg8S&%XeS60dHKDTIWcK7WI<)Zp88V#0hh&Km;BA5YD}+tPtvphX_6& zk0B$O-Dvkik(d7)JVlVA zu?=kFDrsfgJKPd@rEq}0M+}Ai29`uo2R&er;+M`Fxb5z>b!3k<8Yn>h5WR6&Oy-$h z2IKasnGjh{+FID+YhsfO;-+j61@`#+KvbrORH+O(I;LXg=4;BuuB}3)DfZu5)xNfh zn7K^ur#h&ED{l4Va|{tM!#M)S&J*w6Q5EsZm07WCUw3lZ7CSeHn$u|Q5c$Tj2vbFK! z8g-qhlCZ)W8oi67Q1NA+s;BL>x5Z1F(5+hcPKO*M`F=jQlrG){)}bm=aZl=wN124Q)cFXO16y(2gYHICK|NRF2L zN!5-aYrY+uRN@@K{_}cGHEP3)Vra}SLRTfGBh~mOd4cx+GhLDurN%Mhmt*U(0m%NQ0g0baL zf`&^ILaUoo$zRtVlJ1O8-v_+29C_<2zUz0P`p)SU6W6Xiyta`p1{=)No@-1wpLdMA z&*ve+Tf2|1D5fhC{Cur%jeoW8u@vP?`rjivdi=JS%8+yNZjo)3h;!MpNpaomOY_!) zroX?PBkKM<32Q&eW4cq|4T%4jh9L4Ky_i_eIzftdafLKPy~L%k#GAeKeCvZm+*)!Y zegcQ6f(iuMrK%&s$-;2FqFX<@kp#?YUKPH&*#0v0P1YkucWY!NS)eP1TEhH78)X1p za}@U=bBI*|l$v;HuhJZ8oAgrq*=vG)6GiNhC-_f6Re&|80c6@wP;vmW(cV6WCfYg1 zX&}YfpE_nt0b-Y7FTr=-2GjjGsP-dSer2 zPVCO~4TEAjEkac<@={liR$Z<*(aPeZ#azl5YZ@s1$aCMnp zczE~-6X#dV2@BY4{6S(bt`qx^c6_Wa2Z$+)3FipXyhm2Ev}+q6*$+9Qif*84wTfxh z_*<&^>G81kCwte)=B#FC`QE5@KJ(pX45yo|!NVdiZ1x^+93GZ;G#e$W_;EV*A8L2u z(FAqVrW~1O`ch*4td9ANj+#=BV?QUnkBWW%;f(g`NczmyURLU&PhyQN_BGA|@$~!&hkGT*mUS*|L%!lj)vrHQ|G-tz@5JN|iE)QU`Rl(!>w=HiHLSC9_oDjW%j! zC^J42hNR%*3yy#JUKtRGmac5-BCjzmUI*wK_D{;72ZV01yEM_8;(W>^chSekUv)+AX?y-en{^#FzweC63Vz5rgQ_L` z{vki(27iy>?e1|zQ1DqH1Zp5`DvhF<4)Y>QF>!Kt|uN&>=nHYSZnTA#6I6$F_p4=$Jx#tpXanv2A=wcWrh}}N$bOS712Hk(Pu9>n+s!RMmAY@)Hwo)4U%=A$n z{VD|f4NABEhO#Fn!M0M`t3{yGn9AHik8E(8h>BsmTO@^MrvdXi~EtuNr*STZZ{g4yKt;CJ}bpM|9)Uzn(OWm+dOs z`0(5xzxz`TLEQEb+!MJs>hj_6P^ANz0~LCGFAo^CmqRiF7n7rXg%7^+(Vt){&qp}v z5o*D<2ipX*NlgjGJ7d?A$mp$ zue(C?M?ta87PU2)uO(1wa8vVDoM;|zY9 z+FyTO`H3PpUH`&=m zp9`~5*;#59NJ1ywnQlGFl9hB!WVU&UvjrtS%DMLZ^fmQZ^p}vlsn2L}0j*meb74OM z7`z}-1S%psA!9}x;bkqotVe$SNY3(4_rn z!P9D2&^}rtD8Rxp&F=~%HF4_t60fiA8V+xl4xa1G{e?27V8|t&=hVg-m?cNIN9{h| z7WSCRpe-kvYdmJZzFhKV{VdBk!%)MRq=Toty35D^^^l+Y&!=@!KxH;+TbF#x!fMIZ zF?afv2lJ2o=(V)czm8*4rvVQwZfySB7H@Or9^u2y>C1zQU)IicOE#FS4sF&Dce$~W z#(BBrbE=YgeSEuSKITvP#Pd-nXN`{{_viku9KiiynPbNq#Pqb*4J~|1OD~jx5BTnJ z2^(zAVneil}U$$&B9=s#x^;oAs z_T2C9dpBS!g6;6U#)*)q0V}GDEBPqKn_VFHzs%)4?@0HMT7fV^MF7Z05{v|ZFo9$d9nW}c$xab#*4S9|J+qUF!*5JXH5Hs_=6 zPryW;o|6^L*umyupsYFWrayQ%-`OJN z2AQ2ZsI4FNk~4O8bS#=MtkFlXcA@&L@kFqiw#JGmB~^-UJ#`e2`8L2)7i6NZ3wY{q zZasCHR%(E!j`{)gb5CkqLZgLDLNwHn*|fi!H^@2tYlb)^T~$?QD`BA~ZrcD1YkX;= z?LV&FS524DyOzdEUudh|uv!XX7(?hnFBelr0Z(n|4=H}P2clz7OI3`Am|$BYOV{F7 zEby;J60Z#yuOCq{s(Z%G;P=r}i9T^<#6fa&D$g0P2;lZ%{B&z)OWkbGi;>F#P;w~G zH`bOF9U(B5_%%rykn0wO6o~d|%JLj8bn(kOuexL%x*7mBSJ>S+TCKcD<2RJC$O3CK z@^8ywj1gWdT76TXUzt&B-Oup&zuG(Oe|+m;-l_gpl4-b|1kUli#tR7tk?nu$nK_F- z-Br>)^hv@(Q(<90^|F>FyOLyKNtfLBNX~#>Kek24gK(KFs1jeU5afzK=#awwQiUR>J%_&sv<*VeSp8)YT_OauTCA zo_eSkYnDZZQj2ooKzzyBJQ3eM87jL=Q`uGh_X!1_5e{+8Dp6m{7X4eMztf-QfL}L7 zykGu!I@TK#zegX6b^q{-le=owtB2a_2j|msf+Dv@Y^UoSJMin!_nRJ_w8NM4+p!pD zj_c`E$Q2nrqSd~k)DF{*VmuN*=Wwr=$Dq33&6X_NbVunTBr}_rwA|iVQ&ovi_e*0> zg^1lG>ti-B`jy6Kiw1(>o?Sn9!F$qcn450oLxz!> zioIaD-BAgsmrINe%0_Is{)v<3)6;p*F7KFiqITXJ9g2AIO3=lekB5Lhtr2t_>F&zy zG_j&&&i$ul&rj*#SQ!mtb(Z+K((WYBtaqEoU~bLk#(UDJFGzCq{V8U$@EiXqYeru} zxQ}D=u}UJXQQJ0{SNYxKT=Ha@q>*u|k3ic(^3ES-=gm9EPRQ>v)B|)2lfz&7gI&|r z4f!u=rEZ3GseEI$sxMW$)tg^PO8w@3aO~|D^Np-f&K8mx1|)s&x7yfAQPXYI>vbq0 z)TP+!=)D3X{W03&P|Eghohg92K;9j8L$WDC7zGp;-@%^qMWSaqaZMj6?Mpb;% zCc1{EEJ^zDyR#Zl8zYawX+@DS7zvn-Y*Q8K`!?W>q07dyAF9|d4VIE?>_>pPcG@u5 zZ9Ex|eUP{FO_Ge8n@GP~%x{&#Pvl^qb4ue9M59G@hErUtFPAsx03mHKnM>b1PR#q4 z`w1jA(;sPHZrb(GQ*bo>tyQNzFp$*d zW@9cAMjF(7gJcS|A$ky?h^6#<>b2b1<$moEoB|?L-MsFcsRo2 zuaZg2vpR350QH;ht$K`3{Ov#O`>RA|qg(XPyParfx2ACWc^7^sx01i?2ef-JWhOZj z!*^fAFfdX=7Uo+P*3X(${+wI*YS-<@avD{YQ4HNAM=?l)mmo$)2%-?7jw|iCrWxyd zW>eT0gO(%Ao*8ReKOcnlcw$wK;mci&o*pzA_M4Pg3v{bK&G8X%+pHHGu1k~jpP+iT zbx9$zDcB{jxL;44DKtp{j}*B1fzy}_7eEz0IkS<3^w)D-FduG)QugN4_G~Frz!cZe z7+Y9YA7TC(KYgq`CyTk~@CVn#Gct_dt|k5ZN+TW|fAeFWzr@99T9UOS;K=a2vw;?i zBoxU99tG@gzFNzg^CZ@tNAKhL214ak?v z>PHv*Ds}D}SZnWRNqk%$pez2SiGVav1&LUmZ9PedT7eh@Mk@c3f95PbEqq9;UY+w9 z`uh#(m(<)v+1}j_N&%9pv-XuUf0Nc$N}5A09Advg9N*(}{_-vtbH7La^lk!uj1}an zmPM_p`7+9tG`nZy?VqkX^N4!(zTokDBZ#P6Dfo0FVseNdnzQ5@jG>?Uni|;+ zSaE;$mBfjjW>=@4i^$_`QGygTWrbqw_YNfu@TzPhEqx;87MS+?Wp7McLkW_leMDyc zFC8qBp>gjHr)Dod-S~#c1&U*v*6vhH4OBy|M}BBy6VM3#5vKZoN^ECo5|iMG3?qW3 zNQN%+Qfu>!_%`8Hem7<%8Lx{^3o>Q#ykm)AVFeW#QR&G{Zp4nt#cQsxD{UhegzOrP zHVjx=mp*@vic4fzVQn>zEq&sJh=X`7K9nvjsa``mj1#Jcl2ry=aPV_}N2leFnEv>F zpYVk*{X2Hx8EC(nm1-}xc23Bm^6}$X@3#glu?Ca_%Yzy3*t(6}Z6l$Zh{|QlNNF#? zL)^*Qr-aNmYWT**SlV3e!-s_Uk*#fCho%{toCDeEm&=o^Y)&&A;Y+beGIX;ZpN@bZZ~g*T74tj8Ey&jxp2axnGRbX< za9?@jR(z!kHydh%FZEXHq=a2DrlL^2A=Aa7>(>{wRY3Y}->i~MpZ^gJd5Hs3Oq*iV zHmb)vR#2zooZ4SXH9zQ@!Yg5l$!j`il0C!5WWJ~i@-#>7eBWs#*h`fUwDw0BGX)yA zZ}GeD4%_HU5NyrT#tbq#%Do^wLw|n$GfZHyumfl2k}dQdRRmV&D-9P?vbJWN%+fCf6Fcpc${9t80LOy}?DU3? z$jz5dc0G8N6Z2{3#h26UsqRK^X{{#T{`9}J!+A$?S4}f#N6mzuNELmY`JhokO|Spt zZyJ!l>3j1h(7m==NepMJQ_ke^JX|SJgd`e$OT{U6c08w9M7}fL>h%SK#7mv8K)HV2 zC<_cf8FrX|um1J9kA^dWx?S#wt&UmF&rK<@1Wvi_f5RlgS(Ud0Gj z)`g#F_{^7K02bk2oS0NE1LXr1ga@yd5|iBxC(=54-PRk88|ZkjQGr!UK=&dPKB(|x zTx?C#V8E||6H|<_{~F1{Ag-SzwXrC6AMK=1B?;=(yj|eG>c&BK(9yK|(bf1$sAT8D z0!KpTT-vNFnK|Tdr}5YM->Lvw(TSq_(tzM~wayFj+;7Tp8m~@LhvU>s_z-haLEHl< zcbE`_!EU;$WcW-7Bu$d}-s?iw@hd>vs}X598TF1H%+D);Eu}n$TR#tdZ(l`nt~@A% zO&|%;k;bvs<%CpBvQF-DNy&mrbh>qgL&T9B-R#m6%Lb(q=6|I`U!UhSpi;9GB4G8d zE#+Bhzs+KKEy=@1*Y&U2I)!)CDyrDCrNSFHI)U946$VwA@p#um`0p2ijrX%bKN%i4 z7^RXCmlkAf;TzFrq7~wT9AlxvT~Kb&_gfc7DD*s!&TTxNl}i@MCv%J`va+HgVv!^` zq_r3G+PuSdUa`{lfK?SU@Ks<_hvrpt=lNlR@B=T?VMQ!@%aU z^4&)KZlSkKZ(tF=jTtYq1O$g4+GvnL9BXx-@>=)X{p&~i6soH(#}|WpEKzykH9#3K z#m(3np}d*-Ib2c4w2xO5qcb)eb&^!sG2xb zFSj00d1q?*y_5se$Udh+_>8t8oZbj~sD%%lCMFJAUTm_U|A_JfRgi!rS}8(ab|=af}NynLOXo|!Sp z@l0P$JgRevzO25P?mi$#|7y0zchjC=d$_*=$uDu7l4)c6witV!4!DjRPVejC#FiWo zpz_Y^&yb$?d!PMoS5s?j4-B2S2^V~)MiIm;Hy_KnY&jGVCQdm~YKb@NcWRioBf17= zr9k_T{C>7;b;C$>&bMs%XUd`x1t_-vc={>eU4q7olSlz>H^oeQKVZ*K`qrE;k(}-z z5*-4Pf-WIonxqq4&HZYUQs*o%a~TPEwZ<53)))GWc!&=wYo)?ESA4t5!Ml%+)8eDI zL7uEgduPdg(6mIHKzGb9RD2Otc}IqCJD=eR>+vK0d70^k?|Es(%QGU=?v`q~)9yC<5GsBJ7+vRuv9V7UgE=#T(2I2<0fJ=B7d|QdCOivyF$VCqk`T~` zp1TY&_)msx;pjEqW=PR(hUiM_G|uDjYcbeHfM`-{ozEl!rx^_%RsO!ddz2j4>?WkVGtCQ2E+>R`z=QvGO=)`~LA07E-iaJUc z-Ip&Ta=W-*@r16Mdzx>^^e19jQ!)v=^v(}@9(7!N+*eaUeg3OgXF)LPFdK3f3w4?D z)1^2I2R$L!?I_SusijEXV*HbPlfO0YPJzQbFKXxBXX4Q$zzoK7(FQ= zOEK3g&2cx*_gA5Ui`u?#<+Z1E{C#G5XC3%P@=(3Zd%@nEL+lilwHsewkDf~doaZO^ zrA|koRV~v(wWr<9tA9!HVvujc`Gsi3$jBuBJ~3Gol$!e?Iy(^;odaP1RiJ*M(!;wI z2R?2Sq&AN8I!wT3%$1*lEk(W{-|N3ow zn%S66naL02jl?hQT{#P@zhcq+EqKp=_%=z)yn$<<3~gwvK7uRdwDr^_>$}GUPgSP( z{2=U!iPB-f;^TGCRm9a4Uu8dRB~c1f51*Kk5k{pwe%UKJC$>}6c-_(wO`EP508qcd zuc~C_c4K|E-<~DPCv(17wL_0l2K-$CLe<%;Q(Nvz(Z#==NW_Fm_8(zB<*&8ijR(s< zyXIO`WjM@D+v0vM{wDoh>00FmLyclyG_Ni64J0 zs^USF}WV2bgqWV9`MeS+-HodV->~dLl^y<=Bmp$8qFti ztJjSDbyLK?b%tZy*#nC~;Z$?gX!7wl?bxl1ZR16i55+Ho8Qpgu_N0uLG=+ftUpY1B zLm3se{=BnK8N)N|DCBo)<>kIF*E?m@92KUYHIik~#+J))HD@Qs!OZf$7Z*x8TIVjh z8*o^(ExCsAympwihi1G-N?sDJ+h{#%_SQgFd>AnV5V;p?OC{obOP_yUe>6tYNt|Bs zxdx0YQX7c8Wyol(sq^~UeCk=(s&M8DaeoJx#fn-j#vsstG;RJ5gh_67l)5U*R@Aq2 zj?r!J61^L2guCan0%I7neSs6(fD4tZXg1ule_~Z#L3Oe_Mdfa5xyH;^GI20kU^BUJ zg-(fXvZjC5m`lGu>nJ<2enFz6=>&6C!Ja7uz?%yF%j)!pcF>Pil*LSqn_i>8bi>Ux za4+upnkG|IwCLs7Cd3WHKO8uWDHWcMt$aa^+i2 z_Y(s!LUyN1)@L)0LxsAa)_jQuT8D&41Am{g7#q-vOq(4d4EGn)kxCmTben+o($`9g z6e7Zo%6E<88%X%51_O(h9fE`6$kRkxGNc^kzKV--dZBfSha=I0q!9&4?c%7U>uO%f z-C%ZxZ=9{{CcitxR^qoL=B?Jp6EX-f&sh&FVbC~KSsqb4*bCUZZ`=kdjenK$>&;T) z%!*r{^l#zl*MF+OF~8p0Z51H8tpeC&I==&P_EmWH(quixc=L#xrqn}KZzl+$-7X z47crWqfjlV^3vx0upY_PJ;_D-!v+fUhs*Xy8Q1r`M~LK+DY}DKvm0k&ME2bH_Z>en z#OGz7PBf1nAWYu*U0`p^c41F8+Dl5wEMXZKw=@2@>5(hSC^B(#SCaQUe3C5QOfHFO zWwK_i;GE>8ax}PI)p}A*+|)?p=x0gD;6NX)aMRaP?-AWugm5I9SPGn67qX?eCxWN2 z33tl0hN+&h2>bL>pf95*TfW95WVu~857YIfUCAQ`apv6%51&j>!jC^p+x_B6(f*T(J>!_izDNMLxFM< z;$2pI&hr{mlN8g_sMu*vCeIIb(3vc3_Zb+glvzX85<#CneKL{MK?|(N2LAg+_&Q*h zlN|i09{>pR7`MWFjFo7rMr8l&gMMU!{e@KA(Xy`J=v$eaq3R+<+Q|<<`CS!wD#BJfvxOc+yIS+{V)-D(bF)U2S9TEq zkVL>{fo&j~Nq3l83?Ag$i3b~x0LvK|EpDXd4`Hk~i>mO_N!a%HJnk`I73yZ1aV<*i z`nM*-nCb|$=y1)&4Olho2NqEF|1F^4{gdNe=+U-r_f7tL&2Wtf?RuWzaS?`%SqAz* z*R;T1I-NBM$QY}*W18nC)$83gQ@|B@KOAnZ8Wt9o6Q|m6_$k8$cpv+^P?O(dml6x> zi^qmhxdSatibqsva*5~x0;JYMMpQM&s*P%5y!jjxDu;F-4fSX#G-(xhI66DF{4L#X zjT50r6_LVojH9#*cJG65(40&tAyGhxAMa__8h+C+gyD0r3AW7k90?0$tr7Q*F3JD) z`nyiKPq+|WJu(Pcvyu(&?*3-7)5cKc(`9VJD_-nKsh%PL(r7h1I2@)I*k3Xfmmyl{ zWo+vilLRheuzLN+la-?8WG|LpJKUlD(~%57teD)+!>>9< zX)T@LCO`hl*0Mxlsu+fr+^BuCh^|#Rj9DjMEY2)gvD6x5$nU!FsfZ0nGLI-%b35i< z^QvR>>-s@+)X!GPtKtxlWAV;7voq#xI}z|f4vqT!5vH7=KfNCoufGM;A}W9J#)`K? zKmF}$6^GXmzb24l!E+~1BJ;r}^nG$gd`adlD}%gG1xx&P3v5O=Ft%~+LahDIabY4Z zly(eF*3zj97q+Pj9?E>aCBEo|&iRs`UmSFv**Py@;1MtB;*LRHe?0X``;;@@v@$@* zgAS%cqd@qmjxQYh7$}Z zBu(^Ic~k03s??B-@pq%$QtX{wUYpAiQbrf1OFwx3|GHvmNdN!4D~3Wqzqj+zaA3D9 zAemzSF|e*_6*t^&ff)mrLL~Ev{Bpb|!4dZ85Eb;8FtCYRLNoGMEW*&vjv8UP-{Z3hPe0`@#wp zp3tihGJNb%g7vpl(dIMnzz|iJyNENEmEzyi%1qI_?X=pvN=vc$+ZVn#HByedC=bjl zt(6q5WaLrecZ8@A^gM~-sb3r%>GJMA{z~~Sf+rGjH`UR0)%JBbs8R+{yf{*QoAwPg7E>Vl!)KGP$T_eFU(ETEPU48$&bm-rvhSi zZ&iRj)qhKwx;_76=Qjjo*uyK5oLkv-fT=33nGxmEqij}$;^>rmZPss0%8m2fe*`~$ zShA3_|C#hD*2B$5x|4xfDOa^#dn82EcF(x&FbD?R2hNsIvuhnSYk_z6!Vj ziz*R@mkS0ynE{Kd6?-x1zLgnMvVP>r5_A$8V){DqQ6CrD8AcVycb$Sa>{?!;h1IJx{VdXr7n@xB=rch7 zh}Ziq0;IS9d}dme+~mZc``P0?s;des4V0Zx*mH8o6S2>Gab-HO@}P%5SLD=b=5UV{ zdNj397W-Ek%{=sY(q1>RcVX?eM0`828DCXb{N~UqS_Eqi2rvj)p*2s7FMm&oo?J_3 zECgY$5+nB+E9fX{ya{g07UU=Ue=+vv;ZVQt+xS>QWJ_f?_B~56 z+4nUuqOu!8$d)zBVC>oVD0{ZbP6*k<*thHzGPY1mjBSkH%k+7_Ki}u~9LMuK$I(CL zkC^+umh-x<^E&VN-azX1sQ6FZDrliZo!rdO^~x&O$~0R}$MxUFI8EU5uwyQ6$$I{{9boS#X+>cM^uN${w z+?*wT4~f9)*qCxAuN{QaFK>#d?$sZvUUKNj8hAw*DP4jnfOEcVpQQ|3bKNy}-@9=z z6z2B&F9DjJf&Nc8$f_ZF)%weyY+5T_AEB1@Bv{j1%H@b!#&q5a4d;1iz5mN1H<-Wh= z%M%LLZRiR0Ih)mCpX3^ZMq>863o?Ui&>1|&xaU{iY)N+eRhZ>lniz_wG&6z~$C z#rO2JQNVr>`%mwz(9OV64J6KOcQz++X{-DvnO}9QdcGPS%|_I}!rmYpbHm2ayPqCV zul(6ryP6kF{ItO#Hg?1*QX@U&+g4CFTAiXsrj3epS*~u#iDT zV~32w$ueF#M4SuhK2(s(e94L#Okd zEIdndVDz&Ss}q-)k9cmY6X^S*kKAlz;qp0U4-#iop!e~aZeM3`+vm^sZT4^pCBCy? zd4lVjbaH1{A#GD&i$38?U{|oep5jpE>~;Gix1IoMugxN+DC0{*>99Gu(L`kr2$A;0`%BWPX0UK}oM#;ymceEcH zbstu{b{43T?k-^W7>>~yTFtiH-+h00JV3p&MuRiqqu(;K15X3>&nh#eimUwUJA&Z1 z%jwv}wX=a9;#4fq1!wnad&}G9=kxQ;J>Ks#aD+V@TU3PK4Z@?s`2$g_pVdD6%yl5B zcOXOFj$E$IkHq(P;(GS2UG%3v#VxPf|L#-;JNxK$<>+JY7K{&5FO7enDR6h3?rY({ zO<8ju6kijT+iSnzHP=xdb*H03HLsJ%XiCEvW39J%z{s|VCBDIu4~&gI?nn;#AIXLH zVPDYZfIs);8WADez?{rq5d^BIZ(99M2o-6gHfbK8pD-Eb2GhD#DI~3s$8*_`6GRRN z5!`t=EK$YxvF}sv(jk{6l*VH^F!R=)s|5L6bRu{=t~A)AJ*Weq!R5p`o{cCYzgLNs zuh=xuEQuZYc#;2y4O6`DUA6r4<=L)_GyiJ^bp5eH6H<)zdEzdwa$eT@K7f9MVFJ&zvJAljQsp&$R{kbnt}2{8R0D!_~#cb=s- zmUxpcVBld${wU#T6eLkp`Dy%!uhZteuLk3-t|C&duM)CMzs4tGH1fZF(DQXRdd z1}v8jWn~qfVq0G{JIe(f*MgZowh7YF%g0cN1XH!My^jr0v`o;R@jY+2+qXzp*?Sz= zuI?PcEaoy7wPC7yuK4==@|~a;QL^8njBe~;;G(BdQ@szVTVrOxPEoC*ma#&SIOklb zP4)X{^54xNU<;?aij3z&tO(hYdp!f0&Q@oGE6oCfyY7dk(m2I?3pmzZN*09u;yI=G zC-N=McEqy>uHP@OEatpecfKeAdkZ_>5iaYOmd4+XOyMOZO}#*lY9ZDt84pKL5~0HjE{Iq*l7SGq^DMP?Rl4UmRs zZkEpW&(sslt!LX@4Ky6pMUQUD#2M~Q@*TYG7C&}PVS!QV_W^s1Hx})lUMMF43~Bnm zWAq;~gws_QV|3ts0k%I=2~(rY1h|j!xBAb0%o_52%4PTLf)H8Z&w-WKmqwM@2Qr9H z!^hm?ajZHei zX9`8OVJtKc>DP1I*zV*tvpN}-cb0_Bt*?eGzYw|pSa0H1@=ITuJpq}WV%1<`O$`oC zB)n@f_1Ugf?Yi0*x&&?82X9SC>)#ux7xS|}S>Rpor(3WwNOXsX-7k5hnmw1#d$-i3 z4_p@1-OG^Ml@hAoVD0qbJvcgi*r^(Q=<4EdnUvYyO~k8+)Rs`_5u63!2( zr-U>*6@x^5cD}~wGB><7A}j)PwUukt;!YT^ykWg^i;~2>$$II=^F)iiCv3Nx-J6!^ zjmf7*okK|^2e~-qZ?rt$`8-X@T+IcU@+9bTNhuw_KlQUf`D~s)v32%C5D~<6k+hCA z3aeU3<3fJh_h;T!`dBrKpnk{CLxeQZ!-+n&G+O_U;o}FiT_!6_np${ zr2%25weygpGpQtpHhsb_{bpwY;yB>!7zjfei%be~3v{}w= zL|7G?ha%)0-y3eF(6%U2z0>%tSpK1=U@*}uGXynz_TqMI5CJXk%iU~x+Q}a^zd_=A zs#;!tYGCWH*v9ma;L{(&8F4Un&Ve-b@aHc!_hz5uGSodD*5FQ)rdx-7A*?T~6?K2Y z`*`w==%-S-+Ond7D zarPs1*K&}%^jDxMQ@*t$sYU~v_tI;fe@U;tQqO1s(!oa)lL`)r5c1V~ zNxg6$7eE2_rLL<+))JF;?N4F}*GM_+7}mgFNW{@H zvHn%}gD&d;pbCr2YyAVND|>%}yUW{uFGZW{GDmngHw|GpC^Xb7`LW-9;k=m31;o{e z$DN_@x1U}hVhmu#0zB&9p*00o-JzVV#rIYGBlaQMygO#?smF`kO|MWRUu9E=#GK?u zM}RkTr!ePNG6F_T?ZL+xZu@`iUa8i`Qy$NN|Ego^B*LyU=&KEt5%m5pr(t(>mc6SS zqQtI$l`tZRf5WpR$y{4G+=HEljF?dO1)xU#dQ3gDQW+6`NJ~LKFZ-$ZbH-OmR+-s9 zfhV&%tpN)!Eq1TLE%*rK4!?T(obEK5y^8<|68e_X6KvOoaU$Mhc3cdqEa#}B!d?p3 zj7&|fT@T~&US`a681-~GK7h>>R~Ic#HVLOm)AjI_~| zzK<#vV!L&8KlkC*Pz}d9l3hq1{Dj+rcSC9ahtm}pu7*R_6nggb=C`okS@j!?TAp)} zea5&~I*$~VuB+dAkmeD86+$e$B4O-8a(|DfJ6oL6fEvc$Ri#7SB4M%pMG+9fWLrR{ zINQhRz7W5aWXre$&MJ4xuDo3$FtYzxMt0Ipw?A;sIw6-#p_!)j$D54c_~Oz`Y2WfZ zdP1xmIRQ&_z7ji)!!2U+Gktx+iX`*YOAL`jz;~zN2jsaD(j9oeP_Qd`d}`Mm9%1dNZ2ZB(GJ~ zn#WP~UOD1h?}B%4z80Hh_)Ky=ZM%kJ&S+^jNi|nIYg}Xb&j(rN$E+#B9s;n2sZ!jk zA*Lp*?Wk6u&6$QY+P{LUDpufhf46w0+nEYhTdqSNK!bBk!iwF1LRRxEA=Nj}cgH&w zt4KD$5ugN>3zL%&xG;A9kVjgT+F@5#E{>cm4FMSYT`}7CmuQw^U5XX=f_|2pOfLe3 zl4Gjknc@uHx8Q~6FtgQC!fipB{V~-#Bch|j&1@ZVkQ8nHtO_Uwv!)U#6$wKxtQ>#nFY&mi5!I@;jsNHi z>4jKbRid6D@^fHIiISu<387mcF`As@NynY3A#9CQv7&-!2Kzu zEYqpj;P`|M5e%hiLf^@iC?va^4pkQ!p5f#>MH%gl%7hC;K2U)djv^RT(TObFj9vw4 zRoGj%c5BqB!RIlfQH+U_+YXAm#i=LutqukVb!IIK+HT>rWL#CY97 zHpJ%Rd{S*FJ)o~ylc0NSG#=uME{(nEXCXFcBLXaCQp#(cyvsg9;^j5>leS+_LxVm< zo|C%~{g?uE&|SNTEYV5@U_dkddWzEkL$?q99E#b27a$Cr!+(v?IyCy#QNlhT^)h7} zU$R$$j2~ILp0kLt2mEZEeoP1DC;qtcCU!g1CZXT&4G2L;;H$2rs;v&v^rHpwfveZy zrYluMnM2wj%lo5c1O_VWMRRo(eJ2Ogyj2WKHMq078M5v7lUFA`K4!tY!G%TeUZj`B z%!ckq_&;Wt_Tml}EyMvg_!a>_q9itj5K2~&Fl@GDuDayhNtuv9zuf9s+s!s>87cL&74w(xgc)2xW=(O*IV*fot`<#`L5-yT}Gjx zX%2s{{$&F%kYl{K%tlOxA^z63vwm30G@JI_MMY_Ts47>}Vd$=!S&M@7o$L}46OtZ0JEP8`hMS* z78G!)v~Yd2vHLrGa^R0;PW1$o=;&J#M??9W#~CYMzRYlJlm2yq4-QF16!7z(M&{J^W=7sbw-3CV z=#KhODGg|W3Y>p=iSFL$(zL{lheZXxkfvmvn_x={Gls5=XIhGLl`^FL?x})X8~X32 zF+UMz#1_fyavYf1Q)21E;sGI0tLk@3f5Pa&ejOnnOs0uTP{eH=_eW*^)d58@a#3&4 z(5t!s{Bais@8J)u7_6GMGxM08VFLc6Gg`3{;0;-{sPvv)7+cpPJ~yIP8Y|~(gdlBv z2)h1n2zvK}qUCQ0in$0u^>ioRTpg)YYC)q)^cyP}xn+%jZSOol=p}ex{J6XDHQpi#IRnX&gw++>}4x0kd{CR_%NRw4h z;5}CuB4Yz2Mw=h&oC&Z^uPWB}G_RpWgWJ;P(nS4HHL8hn?oQ>BJ)+Y43@e%qo9f57 z9=B(SslA-e?;KYMUmy%&^HQxZYObP?Z-_)Mf!MfPYNNB^V}tG69Mr3Wg?wyV$N~ze zj5$m1JL-Tw>L*-}E3u4t!taw^@;!b%{d zEn4Qg!$^NO@O>2^UQ$D>B*==;&&P_kWwtWqEASB(se{hWxDlAP8TgOQ^Zr(UlY&%` zfxKA2`Tl0)aP22U0x4QVD#4Ejh-)-#Zq4>JuOG zc$SRV6ig_|M;N!CGrHRw4MN>?9B}ct9bV%a^Omo%>3fz(j=~VSW-+u#M2$d9$gy zJte*y<1=j=^=sWf$cW;Vi3^8o{I0oFJCvWP^RwJpV+DYYq8A zG-+em{r=`Ev~TxzA2{4Xt6`VVJSo+se=IgBJ=nJ^MB!T^c9TUPKA`BI{q`~QG^WR>z`^9XmxjbQbaex(HEE;A!IaVv!G&bSTfYu!;ch_kT|r&16e zZLYnt_hCZBfD+23FI(BGgHo~Hpt9#^8ALM+rAfokcgcVT@2|+wS1unWpqG4~ZRne= z2L6lo$FAZ7%*&5aFQoAhC=%a9qa1g+H}}zPCetCZLMi`%J?S}T$4}e!yB=G;5gs+D zd+Me(REooawY@9~()z2<`vJMs80xtLYmvOT^8L+MI#hwlkF70mtEBQ?DA>tSiFn2p z$at#DkLlNN{tO`#=OQ%H+7NdgD#*1c@-mhZmJ+gP)n#q-AEDGWi4X*=ce8g{+4;uf zcyWKi{wT)IFyc5YA;f30X*)WjH-&d*RVT3RW={-kE-AR@eQd?igfuR>)ILW_fJ)0n zJJGT^CQI7pZCxXQC7GCr9x-zFMKEAz`O!4|`>KHGx{8PHHOov&yzkorPr*H^EfCWO z7Is%zJcH+lUezCEok<{$)6+4hXPCO5x7t*Gv%R4K@J)gu?#LV@+PZJx!`U%HPVm!^ zSm{S_OdhdVKeOh^oirKed0YE?Sx*A0EM1OtHb;p_pHvxw`Yw_@KAz_n<8l7Yo46Nu z{+>5pF^qD`P52l%Z)2^<9Ho43jwFEcx1aMTT(Qa2^#MPiyjpjQO@3?OlII_lTR$dK zx5BB%w|GhYlYsC`awoYc%v|QsaU=m-)MV`Y6e;+7fFWyOkCUs5)Dv5Zli;(@5-kiw z^8Wc!wJqc`XYk7iXMVFz7j{)3KPB$tErOCx$19+tF^x;Yo|_wQ98*(@>BRxr)y+$_ z&czxoBpEachvEAw{=g}7g8wadVDn9 z!dBEk?3c-blPyupAEO4|gdy*jw2QNr#eZD`nz40b0f@@;0Ak;}xOTZ$@*RmEp^mM5 z=iuKnqrL_~&nG%b!Pz-vNkC;24T373dF2%B#BhS7+1ebES!LaF(*y1+-L3O24yC{E z>`<1+zWFo`kC!fvcP;*+Z+Yjp16MQt!0+NklXV)6vm-vb8L_w)TqpbBL!6?5kWNrf zzvJ-+CF#eRYtHjIo}#xoqyWng-H%P9c&PWXsdplRPeJ+t{MjE>4~3!lRYSSC{EXmz zj$uX{m}hU&8d?=Pas!M`yz-gJrlQ!ULR-^U40^V<@OY&8$DVa1Qn>*rTs9ybGHf5 zL=r0kXZ$nv0??mi3ve63f;s3fj-{jW;(@)E`QK)9V3=t@rG*nXVD-9A;_y1fuNbt) z=>Y-hHfCi9AKdBmq&S`n@ZQS=V5Ghg$3Y?0hvqhwWEGwli=YLr_^~>ps zvJ4n*u7Ks8Du;o|K|$_IAq_<~ecPmAa7{4Y*a4gWK>Rn(e`XQ?8|TUKaXzNPz-y(= zLSQ4pm3_?rqL;w1Nj8M(O=TN!^oS+poOu^6D>wZrm6QiY&QZuB;CUpmsy;s1Y+FMF z^N6dVfQcW8O{Rivs|C(yLk&&=WU1L?I?ZwXKzJ9GSA$(40@PY}jZ+i0yZU%>D40T0 zGE4L8K~fJMS}Gl$IsIMYKI0A>7(uDi{0D!6uu8~Hi#2qZT>mo^fo?52k8pF4@uOT* zV)pbHAl2$qqa3%(P8-xN%A zL2z`>`3J_^&Pep?qqS0@qJ6O9{pDkYKw(Sjq&HVr{wZ+rjDp7SOs?L)4x)U)hR~^b z7~}f2QRq_Z7*Jh_5^8gF+|#tSpu8%#3Dq<7Gj%oP57ss?hx4G4XPVK40~L2Oay3M* z$@_1=Nb|HCk>OSxsEXlh^GB`1f{MA$CwLOeKAO-8K-Oj>mXZNAR0ZS7Y_@=1e~h^_YC~@GgXge%$g3cM$A>EM|wRUF1v_bA6X=$*UuW$Sc(Q++EtsE^Kn3~B`(v3 z*>25eO(#$FbW*9w#U*zt_6PK$!#>%rE}AqMk3}U0_l%f_-<(r@gsT@hMOvxiO!}&v zYZEN=h&O%%Go4SzYgr2ASwDJxzI`OVh`B!#|83{v^}>Q+)gv8YY33xN@MBpU##Un z^0fi#6*?3D=^{;NDgZsTe5~`M+;%hrv}_{#fNL>@2D+v@ga=>#^k5>A_yuU1=bAP05wW~I|&j0b`stnb-CgbUh&0Sk_J2BI0&)e z9p~ygK4=4#-|t4)CUCUDLFIuDTlR&1L0T=OWyV&Pra*i-UIrsBK^#hfu^>xKDu`Ed zLL1a%1s`)<(K!9sZdp*)&cFzrq zi~P*@a&l;$c9H1=iKt0GUURNKneYo0PIDYjHdlKQKan@&28_3<2N|@MVMr;PmatmGM-NT!OF15t)^qJSj)&J^t9%4~E`r?1m_xYO)?imW^l+p*` zKKCA8_QpUupg{A0j}T<^f3085XqPB<*%e1{agLKumAk|-7mcqd%wqdAb(E<}%d^@- z6_6$6E-Dr38pzu5S`q_M40N)uBFZO2%n+3C8xcuBLRLvFgTIlKo4@Kwoey?sKXWe0 zE6)cjZ<_v(#2bT8yfacAM1j8z;TFnzy2Bl&7-J-W`$1&f5umz2)75w^Z=+CJ zmEFA&dIdi{OQL+>XW^tmO{x?&G>zvRl0ou3ztD`Ww)3ig?I??dFFNlAYqdx)Acvn6 zg_p$iQm^ea53-Fi-i?k!j}DxcU>GL>Ma*N*{w9Y6?kpP#TSTXUz@i2dcc&xWzB5}A zD6&M{iKdxpOXZfmFwHo@J^#6V>x86-S2na1rZhEb#%%b4!Mhy$++mAfsJ2p4q0W

>hxxW?~Zv;Qrjih3IokO-;`Mgz#zN<((wbqD8 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


K8tF5w5R%Z|9p@V^2KEo4NI#)nM1}*-35Q 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 literal 0 HcmV?d00001 diff --git a/docs/ru/SUMMARY.md b/docs/ru/SUMMARY.md index 55a0543f..2b5d2753 100644 --- a/docs/ru/SUMMARY.md +++ b/docs/ru/SUMMARY.md @@ -13,6 +13,7 @@ * [Настройка пульта](radio.md) * [Полетные режимы](modes.md) * [Настройка питания](power.md) + * [Настройка failsafe](failsafe.md) * Работа с Raspberry Pi * [Raspberry Pi](raspberry.md) * [Образ для RPi](image.md) diff --git a/docs/ru/failsafe.md b/docs/ru/failsafe.md new file mode 100644 index 00000000..dd187dfc --- /dev/null +++ b/docs/ru/failsafe.md @@ -0,0 +1,13 @@ +# Настройка failsafe + +Основная статья: https://docs.px4.io/master/en/config/safety.html. + +Во вкладке *Safety* настраиваются реакции квадрокоптера на различные нештатные ситуации. Рекомендуется включить как минимум реакцию на потерю связи с пультом управления: + +1. Откройте вкладку *Safety*. +2. В блоке *RC Loss Failsafe Trigger* выберите один из рекомендуемых вариантов реакции на потерю связи с пультом: + * *Land mode* – переход в режим посадки; + * *Terminate* – аварийное отключение моторов. +3. В поле *RC Loss Timeout* выберите значение таймаута, по истечении которого связь с пультом считается потерянной. Рекомендуемое значение – 0.5 s. + +QGroundControl failsafe diff --git a/docs/ru/power.md b/docs/ru/power.md index c80edf41..10c442e4 100644 --- a/docs/ru/power.md +++ b/docs/ru/power.md @@ -31,3 +31,5 @@ Дополнительная информация: https://docs.px4.io/v1.9.0/en/advanced_config/esc_calibration.html. + +**Далее**: [настройка Failsafe](failsafe.md). From 16b2538dfad9dabd980bb82ea5d85c7615cdb6b8 Mon Sep 17 00:00:00 2001 From: Oleg Kalachev Date: Thu, 20 Feb 2020 00:48:45 +0300 Subject: [PATCH 14/14] docs: commented out links to lessons that have not been translated --- docs/en/lessons.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/en/lessons.md b/docs/en/lessons.md index 244b3f49..76c9a915 100644 --- a/docs/en/lessons.md +++ b/docs/en/lessons.md @@ -8,17 +8,17 @@ [**Lesson # 4** "Aerodynamics of the flight. Propeller"](https://github.com/CopterExpress/clever/blob/master/docs/en/lesson4.md) -[**Lesson # 5** "Brushless motors and controllers"](https://github.com/CopterExpress/clever/blob/master/docs/en/lesson5.md) + [**Lesson 6** "Fundamentals of electromagnetism. Types of motors"](https://github.com/CopterExpress/clever/blob/master/docs/en/lesson6.md) -[**Lesson # 7** "Operating principle, types and design of batteries"](https://github.com/CopterExpress/clever/blob/master/docs/en/lesson7.md) + ## Video lessons